You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Daemonic threads not killed in some circumstances in python 3.13 #123940
I got a bug report on python-zlib-ng where using the threaded opening the program would hang if an exception occurred and a context manager was not used.
This fixes the issue on python 3.9, 3.10, 3.11 and 3.12, but not 3.13-rc2. Also the latest main branch is affected. Unfortunately I could not create a minimal reproducer, other than running the test directly. Daemonic threads seem not to work in this particular case, where there are some BufferedIO streams and queues involved.
changed the title [-]Daemonic threads not killed in python 3.13[/-][+]Daemonic threads not killed in some circumstances in python 3.13[/+]on Sep 11, 2024
Joining daemon threads at finalizer of _ThreadedGzipReader and _ThreadedGzipWriter (i.e. close()) gives a deadlock in recent versions of Python. I am still investigating on this and trying to give an MRE. The following patch makes it pass the test, but I am not sure whether it will break other versions (tested on Python 3.12 and latest).
diff --git a/src/zlib_ng/gzip_ng_threaded.py b/src/zlib_ng/gzip_ng_threaded.py
index 7fa7249..65bd95c 100644
--- a/src/zlib_ng/gzip_ng_threaded.py+++ b/src/zlib_ng/gzip_ng_threaded.py@@ -156,7 +156,6 @@ class _ThreadedGzipReader(io.RawIOBase):
if self._closed:
return
self.running = False
- self.worker.join()
self.fileobj.close()
if self.closefd:
self.raw.close()
@@ -329,7 +328,7 @@ class _ThreadedGzipWriter(io.RawIOBase):
if self._closed:
return
self.flush()
- self.stop()+ self.running = False
if self.exception:
self.raw.close()
self._closed = True
Is it possible to workaround it by starting the worker in first call to read() / write() and using non-daemon threads?
Wrapping ModuleSpawnerIO from another module inside BufferedReader ensures that the finalizer will be called. The spawned thread dies without notifying the main thread of thread_is_exiting:
@hauntsaninja - What were you using as the test case for bisection? The simpler reproducer using atexit has the same behavior on 3.12.4 and main. Both hang until interrupted, which is what I would expect to happen.
Don't join threads in destructors. That seems likely to cause problems because now you have blocking synchronization potentially at arbitrary places. In general, you can't be sure which thread will call destructors due to GC.
There is no custom destructors in python-zlib-ng that join threads. Where did you see this code? In fact, for the classes that cause the issue, there are no custom destructors.
Don't rely on deamon threads being joinable at shutdown [...] not a robust approach.
I really appreciate your explanation. What patterns do you recommend to avoid this sort of behaviour? I have a thread that does the IO. Are there any well documented patterns to prevent this from blocking?
There should be at least some documentation on this in the release notes if it is going to break currently used code. Python-isal uses the same code and is marked as an essential package for the python ecosystem. I'd like it to be able to work with python 3.13 when it comes out.
Activity
[-]Daemonic threads not killed in python 3.13[/-][+]Daemonic threads not killed in some circumstances in python 3.13[/+]y5c4l3 commentedon Sep 12, 2024
Joining daemon threads at finalizer of
_ThreadedGzipReaderand_ThreadedGzipWriter(i.e.close()) gives a deadlock in recent versions of Python. I am still investigating on this and trying to give an MRE. The following patch makes it pass the test, but I am not sure whether it will break other versions (tested on Python 3.12 and latest).Is it possible to workaround it by starting the worker in first call to
read()/write()and using non-daemon threads?rhpvorderman commentedon Sep 13, 2024
@y5c4l3 Thanks! I will investigate and report back.
y5c4l3 commentedon Sep 14, 2024
Reproducible:
Wrapping
ModuleSpawnerIOfrom another module insideBufferedReaderensures that the finalizer will be called. The spawned thread dies without notifying the main thread ofthread_is_exiting:cpython/Python/ceval_gil.c
Lines 294 to 302 in 1de4613
Simpler Reproducible with
atexithauntsaninja commentedon Sep 15, 2024
Thanks for the repro! Bisects to #114839
cc @mpage in case you have time to take a look :-)
mpage commentedon Sep 15, 2024
Thanks for the minimal repo! I can take a look on Monday.
mpage commentedon Sep 15, 2024
@hauntsaninja - What were you using as the test case for bisection? The simpler reproducer using
atexithas the same behavior on3.12.4andmain. Both hang until interrupted, which is what I would expect to happen.hauntsaninja commentedon Sep 15, 2024
I was using the mwe.py + spawner.py repro (which exits in 3.12.5). I think y5c4l3 edited in the
atexitexample after I posted.5 remaining items
Merge branch 'main' into pythongh-123940
Merge branch 'main' into pythongh-123940
rhpvorderman commentedon Sep 20, 2024
There is no custom destructors in python-zlib-ng that join threads. Where did you see this code? In fact, for the classes that cause the issue, there are no custom destructors.
I really appreciate your explanation. What patterns do you recommend to avoid this sort of behaviour? I have a thread that does the IO. Are there any well documented patterns to prevent this from blocking?
There should be at least some documentation on this in the release notes if it is going to break currently used code. Python-isal uses the same code and is marked as an essential package for the python ecosystem. I'd like it to be able to work with python 3.13 when it comes out.
colesbury commentedon Sep 20, 2024
The
__del__forRawIOBasecallsclose()and you have athread.join()called from yourclose()(viastop()).Don't call
thread.join()inclose()if it's being called from__del__(). Something like:rhpvorderman commentedon Sep 23, 2024
@colesbury Thank you so much for this additional information. Now I understand the problem and can fix it appropriately.
hauntsaninja commentedon Sep 23, 2024
People are leaning towards not treating this as blocking for 3.13.0 (x-ref colesbury's message on internal Discord)
colesbury also mentioned #116514 as a reference for hard crashes on 3.11/3.12 (I wasn't able to reproduce #124150 (comment) )
pythongh-87135: Raise PythonFinalizationError when joining a blocked …
pythongh-87135: Raise PythonFinalizationError when joining a blocked …
gh-87135: Raise PythonFinalizationError when joining a blocked daemon…
encukou commentedon Jun 16, 2025
In 3.14 daemon threads will hang if they attempt to call Python API after finalization. Also, joining such a thread will raise
PythonFinalizationError.This will not be backported to 3.13. The change is too invasive. See also this topic: “In general, avoiding new bugs or changes in behaviour is more important than fixing existing bugs.”
AFAICS, the immediate issue in zlib-ng can be (and was?) fixed by avoiding
thread.join()being called (indirectly) from finalizers.Looks like there's nothing more to do for this issue. I'll close it next week if there are no objections.