mirror of
https://github.com/python/cpython.git
synced 2025-08-04 00:48:58 +00:00
GH-110829: Ensure Thread.join() joins the OS thread (#110848)
Joining a thread now ensures the underlying OS thread has exited. This is required for safer fork() in multi-threaded processes. --------- Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com>
This commit is contained in:
parent
a28a3967ab
commit
0e9c364f4a
14 changed files with 676 additions and 103 deletions
|
@ -160,6 +160,132 @@ class ThreadRunningTests(BasicThreadTest):
|
|||
f"Exception ignored in thread started by {task!r}")
|
||||
self.assertIsNotNone(cm.unraisable.exc_traceback)
|
||||
|
||||
def test_join_thread(self):
|
||||
finished = []
|
||||
|
||||
def task():
|
||||
time.sleep(0.05)
|
||||
finished.append(thread.get_ident())
|
||||
|
||||
with threading_helper.wait_threads_exit():
|
||||
handle = thread.start_joinable_thread(task)
|
||||
handle.join()
|
||||
self.assertEqual(len(finished), 1)
|
||||
self.assertEqual(handle.ident, finished[0])
|
||||
|
||||
def test_join_thread_already_exited(self):
|
||||
def task():
|
||||
pass
|
||||
|
||||
with threading_helper.wait_threads_exit():
|
||||
handle = thread.start_joinable_thread(task)
|
||||
time.sleep(0.05)
|
||||
handle.join()
|
||||
|
||||
def test_join_several_times(self):
|
||||
def task():
|
||||
pass
|
||||
|
||||
with threading_helper.wait_threads_exit():
|
||||
handle = thread.start_joinable_thread(task)
|
||||
handle.join()
|
||||
with self.assertRaisesRegex(ValueError, "not joinable"):
|
||||
handle.join()
|
||||
|
||||
def test_joinable_not_joined(self):
|
||||
handle_destroyed = thread.allocate_lock()
|
||||
handle_destroyed.acquire()
|
||||
|
||||
def task():
|
||||
handle_destroyed.acquire()
|
||||
|
||||
with threading_helper.wait_threads_exit():
|
||||
handle = thread.start_joinable_thread(task)
|
||||
del handle
|
||||
handle_destroyed.release()
|
||||
|
||||
def test_join_from_self(self):
|
||||
errors = []
|
||||
handles = []
|
||||
start_joinable_thread_returned = thread.allocate_lock()
|
||||
start_joinable_thread_returned.acquire()
|
||||
task_tried_to_join = thread.allocate_lock()
|
||||
task_tried_to_join.acquire()
|
||||
|
||||
def task():
|
||||
start_joinable_thread_returned.acquire()
|
||||
try:
|
||||
handles[0].join()
|
||||
except Exception as e:
|
||||
errors.append(e)
|
||||
finally:
|
||||
task_tried_to_join.release()
|
||||
|
||||
with threading_helper.wait_threads_exit():
|
||||
handle = thread.start_joinable_thread(task)
|
||||
handles.append(handle)
|
||||
start_joinable_thread_returned.release()
|
||||
# Can still join after joining failed in other thread
|
||||
task_tried_to_join.acquire()
|
||||
handle.join()
|
||||
|
||||
assert len(errors) == 1
|
||||
with self.assertRaisesRegex(RuntimeError, "Cannot join current thread"):
|
||||
raise errors[0]
|
||||
|
||||
def test_detach_from_self(self):
|
||||
errors = []
|
||||
handles = []
|
||||
start_joinable_thread_returned = thread.allocate_lock()
|
||||
start_joinable_thread_returned.acquire()
|
||||
thread_detached = thread.allocate_lock()
|
||||
thread_detached.acquire()
|
||||
|
||||
def task():
|
||||
start_joinable_thread_returned.acquire()
|
||||
try:
|
||||
handles[0].detach()
|
||||
except Exception as e:
|
||||
errors.append(e)
|
||||
finally:
|
||||
thread_detached.release()
|
||||
|
||||
with threading_helper.wait_threads_exit():
|
||||
handle = thread.start_joinable_thread(task)
|
||||
handles.append(handle)
|
||||
start_joinable_thread_returned.release()
|
||||
thread_detached.acquire()
|
||||
with self.assertRaisesRegex(ValueError, "not joinable"):
|
||||
handle.join()
|
||||
|
||||
assert len(errors) == 0
|
||||
|
||||
def test_detach_then_join(self):
|
||||
lock = thread.allocate_lock()
|
||||
lock.acquire()
|
||||
|
||||
def task():
|
||||
lock.acquire()
|
||||
|
||||
with threading_helper.wait_threads_exit():
|
||||
handle = thread.start_joinable_thread(task)
|
||||
# detach() returns even though the thread is blocked on lock
|
||||
handle.detach()
|
||||
# join() then cannot be called anymore
|
||||
with self.assertRaisesRegex(ValueError, "not joinable"):
|
||||
handle.join()
|
||||
lock.release()
|
||||
|
||||
def test_join_then_detach(self):
|
||||
def task():
|
||||
pass
|
||||
|
||||
with threading_helper.wait_threads_exit():
|
||||
handle = thread.start_joinable_thread(task)
|
||||
handle.join()
|
||||
with self.assertRaisesRegex(ValueError, "not joinable"):
|
||||
handle.detach()
|
||||
|
||||
|
||||
class Barrier:
|
||||
def __init__(self, num_threads):
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue