-
Notifications
You must be signed in to change notification settings - Fork 19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Broken thread safety #88
Comments
I had to repeat it a few times to trigger this behavior, but I see it. |
Standalone reproducer for a different race condition: import threading
import time
import lazy_loader as lazy
def repeat_lazy():
time.sleep(0.5)
np = lazy.load('numpy')
for _ in range(50):
threading.Thread(target=repeat_lazy).start() |
@eindenbom Can you give #90 a try? |
I do not think #90 fixes the same race condition as reported in this issue. You are fixing a possible race (I am not sure that there is any) in lazy_loader.load(), while in librosa it is executed at librosa load time and therefore protected by module load lock. The race I have reported is in |
The tests confirm that the other race condition exists, but you are right that #90 does not address this issue. |
Agreed, this is a CPython bug: import importlib
import threading
import sys
spec = importlib.util.find_spec('http')
module = importlib.util.module_from_spec(spec)
http = sys.modules['http'] = module
loader = importlib.util.LazyLoader(spec.loader)
loader.exec_module(module)
def check():
return http.HTTPStatus.ACCEPTED == 202
for _ in range(5):
threading.Thread(target=check).start() This looks painful to fix, since attempting to attach a lock to the |
Thanks for investigating, @effigies! Adding |
Can we simply lock up import importlib
import threading
import sys
import time
spec = importlib.util.find_spec('http')
module = importlib.util.module_from_spec(spec)
http = sys.modules['http'] = module
lock = threading.Lock()
def lock_func(f):
def locked(*args, **kwargs):
with lock:
return f(*args, **kwargs)
return locked
loader = importlib.util.LazyLoader(spec.loader)
loader.exec_module = lock_func(loader.exec_module)
loader.exec_module(module)
def check():
time.sleep(0.2)
return http.HTTPStatus.ACCEPTED == 202
for _ in range(5):
threading.Thread(target=check).start() |
I don't think so, because |
Yes, but look at the above example, which wraps exec_module in a lock. |
In [1]: import importlib
...: import threading
...: import sys
...: import time
...:
...: spec = importlib.util.find_spec('http')
...: module = importlib.util.module_from_spec(spec)
...: http = sys.modules['http'] = module
...:
...: lock = threading.Lock()
...:
...: def lock_func(f):
...: def locked(*args, **kwargs):
...: with lock:
...: return f(*args, **kwargs)
...: return locked
...:
...: loader = importlib.util.LazyLoader(spec.loader)
...: loader.exec_module = lock_func(loader.exec_module)
...: loader.exec_module(module)
...:
...:
...: def check():
...: time.sleep(0.2)
...: return http.HTTPStatus.ACCEPTED == 202
...:
...: for _ in range(5):
...: threading.Thread(target=check).start()
...:
Exception in thread Thread-2 (check):
Traceback (most recent call last):
File "/home/chris/mambaforge/envs/default/lib/python3.10/threading.py", line 1016, in _bootstrap_inner
self.run()
File "/home/chris/mambaforge/envs/default/lib/python3.10/threading.py", line 953, in run
self._target(*self._args, **self._kwargs)
File "<ipython-input-1-1b0c7fd1f576>", line 25, in check
Exception in thread Thread-5 (check):
Traceback (most recent call last):
File "/home/chris/mambaforge/envs/default/lib/python3.10/threading.py", line 1016, in _bootstrap_inner
self.run()
File "/home/chris/mambaforge/envs/default/lib/python3.10/threading.py", line 953, in run
self._target(*self._args, **self._kwargs)
File "<ipython-input-1-1b0c7fd1f576>", line 25, in check
AttributeError: module 'http' has no attribute 'HTTPStatus'
AttributeError: module 'http' has no attribute 'HTTPStatus' |
Hrm, yes, looks like I have to use |
The loader isn't where the race is. I might be wrong, but I don't think that will protect the actual critical operations. |
Right, so we'll have to ensure "attribute access + lazy loading" becomes an atomic operation. |
I went ahead and opened python/cpython#114763. I think I have a fix. |
@eindenbom If you're prepared to build your whole dependency tree for 3.13-dev, you could test out python/cpython#114781. I suspect it can be rebased on the 3.12 branch as well, if you'd prefer. |
Thanks to @effigies, this issue has been addressed by python/cpython#114781. @eindenbom This should soon be backported to the 3.11 and 3.12 source trees. |
Should be out in 3.11.9 (python/cpython#115871 - ETA April 1) and 3.12.3 (python/cpython#115870 - ETA April 9). |
No, my read is that these are two independent races. |
lazy_loader loaded modules are not thread-safe: when concurrently accessed from several threads all but the first thread get partially loaded module missing most of functionality.
For example
librosa
uses lazy_loader to loadresampy
and gets the following error:To Reproduce
Run the following snippet:
Expected behavior
No errors reported.
Actual output
Software versions
The text was updated successfully, but these errors were encountered: