gh-105716: Support Background Threads in Subinterpreters Consistently (gh-109921)

The existence of background threads running on a subinterpreter was preventing interpreters from getting properly destroyed, as well as impacting the ability to run the interpreter again. It also affected how we wait for non-daemon threads to finish.

We add PyInterpreterState.threads.main, with some internal C-API functions.
This commit is contained in:
Eric Snow 2023-10-02 14:12:12 -06:00 committed by GitHub
parent a040a32ea2
commit 1dd9dee45d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 258 additions and 46 deletions

View file

@ -26,6 +26,11 @@ from unittest import mock
from test import lock_tests
from test import support
try:
from test.support import interpreters
except ModuleNotFoundError:
interpreters = None
threading_helper.requires_working_threading(module=True)
# Between fork() and exec(), only async-safe functions are allowed (issues
@ -52,6 +57,12 @@ def skip_unless_reliable_fork(test):
return test
def requires_subinterpreters(meth):
"""Decorator to skip a test if subinterpreters are not supported."""
return unittest.skipIf(interpreters is None,
'subinterpreters required')(meth)
def restore_default_excepthook(testcase):
testcase.addCleanup(setattr, threading, 'excepthook', threading.excepthook)
threading.excepthook = threading.__excepthook__
@ -1311,6 +1322,44 @@ class SubinterpThreadingTests(BaseTestCase):
# The thread was joined properly.
self.assertEqual(os.read(r, 1), b"x")
@requires_subinterpreters
def test_threads_join_with_no_main(self):
r_interp, w_interp = self.pipe()
INTERP = b'I'
FINI = b'F'
DONE = b'D'
interp = interpreters.create()
interp.run(f"""if True:
import os
import threading
import time
done = False
def notify_fini():
global done
done = True
os.write({w_interp}, {FINI!r})
t.join()
threading._register_atexit(notify_fini)
def task():
while not done:
time.sleep(0.1)
os.write({w_interp}, {DONE!r})
t = threading.Thread(target=task)
t.start()
os.write({w_interp}, {INTERP!r})
""")
interp.close()
self.assertEqual(os.read(r_interp, 1), INTERP)
self.assertEqual(os.read(r_interp, 1), FINI)
self.assertEqual(os.read(r_interp, 1), DONE)
@cpython_only
def test_daemon_threads_fatal_error(self):
subinterp_code = f"""if 1: