gh-104341: Add a Separate "Running" Lock for Each Thread (gh-104754)

Having a separate lock means Thread.join() doesn't need to wait for the thread to be cleaned up first.  It can wait for the thread's Python target to finish running.  This gives us some flexibility in how we clean up threads.

(This is a minor cleanup as part of a fix for gh-104341.)
This commit is contained in:
Eric Snow 2023-05-23 14:29:30 -06:00 committed by GitHub
parent 08b4eb83aa
commit 097b7830cd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 52 additions and 40 deletions

View file

@ -747,7 +747,7 @@ class ThreadTests(BaseTestCase):
rc, out, err = assert_python_ok("-c", code)
self.assertEqual(err, b"")
def test_tstate_lock(self):
def test_running_lock(self):
# Test an implementation detail of Thread objects.
started = _thread.allocate_lock()
finish = _thread.allocate_lock()
@ -757,29 +757,29 @@ class ThreadTests(BaseTestCase):
started.release()
finish.acquire()
time.sleep(0.01)
# The tstate lock is None until the thread is started
# The running lock is None until the thread is started
t = threading.Thread(target=f)
self.assertIs(t._tstate_lock, None)
self.assertIs(t._running_lock, None)
t.start()
started.acquire()
self.assertTrue(t.is_alive())
# The tstate lock can't be acquired when the thread is running
# The running lock can't be acquired when the thread is running
# (or suspended).
tstate_lock = t._tstate_lock
self.assertFalse(tstate_lock.acquire(timeout=0), False)
running_lock = t._running_lock
self.assertFalse(running_lock.acquire(timeout=0), False)
finish.release()
# When the thread ends, the state_lock can be successfully
# acquired.
self.assertTrue(tstate_lock.acquire(timeout=support.SHORT_TIMEOUT), False)
# But is_alive() is still True: we hold _tstate_lock now, which
# prevents is_alive() from knowing the thread's end-of-life C code
self.assertTrue(running_lock.acquire(timeout=support.SHORT_TIMEOUT), False)
# But is_alive() is still True: we hold _running_lock now, which
# prevents is_alive() from knowing the thread's Python code
# is done.
self.assertTrue(t.is_alive())
# Let is_alive() find out the C code is done.
tstate_lock.release()
running_lock.release()
self.assertFalse(t.is_alive())
# And verify the thread disposed of _tstate_lock.
self.assertIsNone(t._tstate_lock)
# And verify the thread disposed of _running_lock.
self.assertIsNone(t._running_lock)
t.join()
def test_repr_stopped(self):