summaryrefslogtreecommitdiffstats
path: root/Lib/test/test_thread.py
diff options
context:
space:
mode:
authorAntoine Pitrou <antoine@python.org>2023-11-04 13:59:24 (GMT)
committerGitHub <noreply@github.com>2023-11-04 13:59:24 (GMT)
commit0e9c364f4ac18a2237bdbac702b96bcf8ef9cb09 (patch)
tree8febb8282c2c1ebd73a18205ec5b9229a99ac4fe /Lib/test/test_thread.py
parenta28a3967ab9a189122f895d51d2551f7b3a273b0 (diff)
downloadcpython-0e9c364f4ac18a2237bdbac702b96bcf8ef9cb09.zip
cpython-0e9c364f4ac18a2237bdbac702b96bcf8ef9cb09.tar.gz
cpython-0e9c364f4ac18a2237bdbac702b96bcf8ef9cb09.tar.bz2
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>
Diffstat (limited to 'Lib/test/test_thread.py')
-rw-r--r--Lib/test/test_thread.py126
1 files changed, 126 insertions, 0 deletions
diff --git a/Lib/test/test_thread.py b/Lib/test/test_thread.py
index 831aaf5..931cb4b 100644
--- a/Lib/test/test_thread.py
+++ b/Lib/test/test_thread.py
@@ -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):