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

@ -261,6 +261,16 @@ class TestInterpreterIsRunning(TestBase):
self.assertTrue(interp.is_running())
self.assertFalse(interp.is_running())
def test_finished(self):
r, w = os.pipe()
interp = interpreters.create()
interp.run(f"""if True:
import os
os.write({w}, b'x')
""")
self.assertFalse(interp.is_running())
self.assertEqual(os.read(r, 1), b'x')
def test_from_subinterpreter(self):
interp = interpreters.create()
out = _run_output(interp, dedent(f"""
@ -288,6 +298,31 @@ class TestInterpreterIsRunning(TestBase):
with self.assertRaises(ValueError):
interp.is_running()
def test_with_only_background_threads(self):
r_interp, w_interp = os.pipe()
r_thread, w_thread = os.pipe()
DONE = b'D'
FINISHED = b'F'
interp = interpreters.create()
interp.run(f"""if True:
import os
import threading
def task():
v = os.read({r_thread}, 1)
assert v == {DONE!r}
os.write({w_interp}, {FINISHED!r})
t = threading.Thread(target=task)
t.start()
""")
self.assertFalse(interp.is_running())
os.write(w_thread, DONE)
interp.run('t.join()')
self.assertEqual(os.read(r_interp, 1), FINISHED)
class TestInterpreterClose(TestBase):
@ -389,6 +424,37 @@ class TestInterpreterClose(TestBase):
interp.close()
self.assertTrue(interp.is_running())
def test_subthreads_still_running(self):
r_interp, w_interp = os.pipe()
r_thread, w_thread = os.pipe()
FINISHED = b'F'
interp = interpreters.create()
interp.run(f"""if True:
import os
import threading
import time
done = False
def notify_fini():
global done
done = True
t.join()
threading._register_atexit(notify_fini)
def task():
while not done:
time.sleep(0.1)
os.write({w_interp}, {FINISHED!r})
t = threading.Thread(target=task)
t.start()
""")
interp.close()
self.assertEqual(os.read(r_interp, 1), FINISHED)
class TestInterpreterRun(TestBase):
@ -465,6 +531,37 @@ class TestInterpreterRun(TestBase):
with self.assertRaises(TypeError):
interp.run(b'print("spam")')
def test_with_background_threads_still_running(self):
r_interp, w_interp = os.pipe()
r_thread, w_thread = os.pipe()
RAN = b'R'
DONE = b'D'
FINISHED = b'F'
interp = interpreters.create()
interp.run(f"""if True:
import os
import threading
def task():
v = os.read({r_thread}, 1)
assert v == {DONE!r}
os.write({w_interp}, {FINISHED!r})
t = threading.Thread(target=task)
t.start()
os.write({w_interp}, {RAN!r})
""")
interp.run(f"""if True:
os.write({w_interp}, {RAN!r})
""")
os.write(w_thread, DONE)
interp.run('t.join()')
self.assertEqual(os.read(r_interp, 1), RAN)
self.assertEqual(os.read(r_interp, 1), RAN)
self.assertEqual(os.read(r_interp, 1), FINISHED)
# test_xxsubinterpreters covers the remaining Interpreter.run() behavior.