Since #109135, thread_run checks if it is called during finalization, and in this case it frees thread bootstate in thread_bootstate_free and returns. The problem is that PyMem_Free shouldn't be called if GIL is not held.
$ ./python repro.py
0 ...................................failed with exit code 1
Traceback (most recent call last):
File "/home/vstinner/python/main/Lib/threading.py", line 641, in wait
signaled = self._cond.wait(timeout)
^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/vstinner/python/main/Lib/threading.py", line 337, in wait
saved_state = self._release_save()
^^^^^^^^^^^^^^^^^^^^
File "/home/vstinner/python/main/Lib/threading.py", line 295, in _release_save
self._lock.release() # No state to save
^^^^^^^^^^^^^^^^^^^^
KeyboardInterrupt
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<string>", line 6, in <module>
File "/home/vstinner/python/main/Lib/threading.py", line 983, in start
self._started.wait()
File "/home/vstinner/python/main/Lib/threading.py", line 638, in wait
with self._cond:
File "/home/vstinner/python/main/Lib/threading.py", line 289, in __exit__
return self._lock.__exit__(*args)
^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: release unlocked lock
Yep, it takes precise timing for interrupt to make Python finalize before new thread will be started in thread_PyThread_start_new_thread. The repro I attached is fittable for my machine, but may not work on others.
Activity
vstinner commented on Sep 24, 2023
thread_PyThread_start_new_thread() can easily use PyMem_RawMalloc() instead of PyMem_Malloc(). Do you want to propose a fix?
chgnrdv commented on Sep 24, 2023
@vstinner, yes, I'll submit a PR shortly :)
gh-109795: `_thread.start_new_thread`: allocate thread bootstate usin…
pythongh-109795: `_thread.start_new_thread`: allocate thread bootstat…
vstinner commented on Sep 25, 2023
Ah. After many attempts, I managed to reproduce the bug without the fix:
I modified the reproducer script:
Sometimes, I get other bugs like:
$ ./python repro.py 0 ...................................failed with exit code 1 Traceback (most recent call last): File "/home/vstinner/python/main/Lib/threading.py", line 641, in wait signaled = self._cond.wait(timeout) ^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/vstinner/python/main/Lib/threading.py", line 337, in wait saved_state = self._release_save() ^^^^^^^^^^^^^^^^^^^^ File "/home/vstinner/python/main/Lib/threading.py", line 295, in _release_save self._lock.release() # No state to save ^^^^^^^^^^^^^^^^^^^^ KeyboardInterrupt During handling of the above exception, another exception occurred: Traceback (most recent call last): File "<string>", line 6, in <module> File "/home/vstinner/python/main/Lib/threading.py", line 983, in start self._started.wait() File "/home/vstinner/python/main/Lib/threading.py", line 638, in wait with self._cond: File "/home/vstinner/python/main/Lib/threading.py", line 289, in __exit__ return self._lock.__exit__(*args) ^^^^^^^^^^^^^^^^^^^^^^^^^^ RuntimeError: release unlocked lockvstinner commented on Sep 25, 2023
Thanks for the reproducer @chgnrdv!
The bug is hard to trigger :-( But apparently, I cannot reproduce the bug anymore. Good :-)
[3.11] gh-109795: `_thread.start_new_thread`: allocate thread bootsta…
chgnrdv commented on Sep 25, 2023
@vstinner thank you for review :)
Yep, it takes precise timing for interrupt to make Python finalize before new thread will be started in thread_PyThread_start_new_thread. The repro I attached is fittable for my machine, but may not work on others.
pythongh-109795: `_thread.start_new_thread`: allocate thread bootstat…
pythongh-109795: `_thread.start_new_thread`: allocate thread bootstat…
[3.12] gh-108987: Fix _thread.start_new_thread() race condition (#109135
pythongh-109795: `_thread.start_new_thread`: allocate thread bootstat…