Open
Description
One of the first thing that happens during interpreter finalization is waiting for all non-daemon threads to finish. This is implemented by calling threading._shutdown()
. If an exception is raised there (e.g. in a threading
"atexit" function), it gets ignored and we end up with non-daemon threads still running. In subinterpreters that results in a fatal error almost immediately after, since we make sure there's only one thread state left at that point.
Reproducer:
$ cat > /tmp/crash-interp-lingering-thread.py << EOF
import threading
from test.support import interpreters
interp = interpreters.create()
interp.exec_sync(f"""if True:
import threading
import time
done = False
def notify_fini():
global done
done = True
raise Exception # <-------
t.join()
threading._register_atexit(notify_fini)
def task():
while not done:
time.sleep(0.1)
t = threading.Thread(target=task)
t.start()
""")
interp.close()
EOF
$ ./python /tmp/crash-interp-lingering-thread.py
Exception ignored on threading shutdown:
Traceback (most recent call last):
File "./cpython/Lib/threading.py", line 1623, in _shutdown
atexit_call()
File "<string>", line 10, in notify_fini
Exception:
Fatal Python error: Py_EndInterpreter: not the last thread
Python runtime state: initialized
Current thread 0x00007fc8bf76a100 (most recent call first):
<no Python frame>
Thread 0x00007fc8bd3d5700 (most recent call first):
File "<string>", line 16 in task
File "./cpython/Lib/threading.py", line 1020 in run
File "./cpython/Lib/threading.py", line 1083 in _bootstrap_inner
File "./cpython/Lib/threading.py", line 1040 in _bootstrap
Aborted (core dumped)
The solution? Make sure threading._shutdown()
finishes its fundamental job: stop all non-daemon threads and wait for them to finish. At the very least, this means ignoring exceptions from functions registered with threading._register_atexit()
.
Linked PRs
Metadata
Metadata
Assignees
Labels
Projects
Status
Todo
Status
No status