Skip to content

Commit 4b56e56

Browse files
authored
gh-104837: Revert "gh-104341: Add a Separate "Running" Lock for Each Thread (gh-104754) (#104838)
gh-104837: Revert "gh-104341: Add a Separate "Running" Lock for Each Thread (gh-104754)" This reverts commit 097b783.
1 parent 7f963bf commit 4b56e56

File tree

2 files changed

+41
-53
lines changed

2 files changed

+41
-53
lines changed

Lib/test/test_threading.py

+12-12
Original file line numberDiff line numberDiff line change
@@ -747,7 +747,7 @@ def f():
747747
rc, out, err = assert_python_ok("-c", code)
748748
self.assertEqual(err, b"")
749749

750-
def test_running_lock(self):
750+
def test_tstate_lock(self):
751751
# Test an implementation detail of Thread objects.
752752
started = _thread.allocate_lock()
753753
finish = _thread.allocate_lock()
@@ -757,29 +757,29 @@ def f():
757757
started.release()
758758
finish.acquire()
759759
time.sleep(0.01)
760-
# The running lock is None until the thread is started
760+
# The tstate lock is None until the thread is started
761761
t = threading.Thread(target=f)
762-
self.assertIs(t._running_lock, None)
762+
self.assertIs(t._tstate_lock, None)
763763
t.start()
764764
started.acquire()
765765
self.assertTrue(t.is_alive())
766-
# The running lock can't be acquired when the thread is running
766+
# The tstate lock can't be acquired when the thread is running
767767
# (or suspended).
768-
running_lock = t._running_lock
769-
self.assertFalse(running_lock.acquire(timeout=0), False)
768+
tstate_lock = t._tstate_lock
769+
self.assertFalse(tstate_lock.acquire(timeout=0), False)
770770
finish.release()
771771
# When the thread ends, the state_lock can be successfully
772772
# acquired.
773-
self.assertTrue(running_lock.acquire(timeout=support.SHORT_TIMEOUT), False)
774-
# But is_alive() is still True: we hold _running_lock now, which
775-
# prevents is_alive() from knowing the thread's Python code
773+
self.assertTrue(tstate_lock.acquire(timeout=support.SHORT_TIMEOUT), False)
774+
# But is_alive() is still True: we hold _tstate_lock now, which
775+
# prevents is_alive() from knowing the thread's end-of-life C code
776776
# is done.
777777
self.assertTrue(t.is_alive())
778778
# Let is_alive() find out the C code is done.
779-
running_lock.release()
779+
tstate_lock.release()
780780
self.assertFalse(t.is_alive())
781-
# And verify the thread disposed of _running_lock.
782-
self.assertIsNone(t._running_lock)
781+
# And verify the thread disposed of _tstate_lock.
782+
self.assertIsNone(t._tstate_lock)
783783
t.join()
784784

785785
def test_repr_stopped(self):

Lib/threading.py

+29-41
Original file line numberDiff line numberDiff line change
@@ -908,7 +908,6 @@ class is implemented.
908908
self._ident = None
909909
if _HAVE_THREAD_NATIVE_ID:
910910
self._native_id = None
911-
self._running_lock = None
912911
self._tstate_lock = None
913912
self._started = Event()
914913
self._is_stopped = False
@@ -927,17 +926,13 @@ def _reset_internal_locks(self, is_alive):
927926
# bpo-42350: If the fork happens when the thread is already stopped
928927
# (ex: after threading._shutdown() has been called), _tstate_lock
929928
# is None. Do nothing in this case.
930-
if self._running_lock is not None:
931-
self._running_lock._at_fork_reinit()
932-
self._running_lock.acquire()
933929
if self._tstate_lock is not None:
934930
self._tstate_lock._at_fork_reinit()
935931
self._tstate_lock.acquire()
936932
else:
937933
# The thread isn't alive after fork: it doesn't have a tstate
938934
# anymore.
939935
self._is_stopped = True
940-
self._running_lock = None
941936
self._tstate_lock = None
942937

943938
def __repr__(self):
@@ -1024,14 +1019,6 @@ def _set_ident(self):
10241019
def _set_native_id(self):
10251020
self._native_id = get_native_id()
10261021

1027-
def _set_running_lock(self):
1028-
"""
1029-
Set a lock object which will be released by the interpreter when
1030-
the target func has finished running.
1031-
"""
1032-
self._running_lock = _allocate_lock()
1033-
self._running_lock.acquire()
1034-
10351022
def _set_tstate_lock(self):
10361023
"""
10371024
Set a lock object which will be released by the interpreter when
@@ -1048,7 +1035,6 @@ def _set_tstate_lock(self):
10481035
def _bootstrap_inner(self):
10491036
try:
10501037
self._set_ident()
1051-
self._set_running_lock()
10521038
self._set_tstate_lock()
10531039
if _HAVE_THREAD_NATIVE_ID:
10541040
self._set_native_id()
@@ -1068,29 +1054,29 @@ def _bootstrap_inner(self):
10681054
self._invoke_excepthook(self)
10691055
finally:
10701056
self._delete()
1071-
self._running_lock.release()
10721057

10731058
def _stop(self):
10741059
# After calling ._stop(), .is_alive() returns False and .join() returns
1075-
# immediately. ._running_lock must be released before calling ._stop().
1060+
# immediately. ._tstate_lock must be released before calling ._stop().
10761061
#
1077-
# Normal case: ._bootstrap_inner() releases ._running_lock, and
1078-
# that's detected by our ._wait_for_running_lock(), called by .join()
1062+
# Normal case: C code at the end of the thread's life
1063+
# (release_sentinel in _threadmodule.c) releases ._tstate_lock, and
1064+
# that's detected by our ._wait_for_tstate_lock(), called by .join()
10791065
# and .is_alive(). Any number of threads _may_ call ._stop()
10801066
# simultaneously (for example, if multiple threads are blocked in
10811067
# .join() calls), and they're not serialized. That's harmless -
10821068
# they'll just make redundant rebindings of ._is_stopped and
1083-
# ._running_lock. Obscure: we rebind ._running_lock last so that the
1084-
# "assert self._is_stopped" in ._wait_for_running_lock() always works
1085-
# (the assert is executed only if ._running_lock is None).
1069+
# ._tstate_lock. Obscure: we rebind ._tstate_lock last so that the
1070+
# "assert self._is_stopped" in ._wait_for_tstate_lock() always works
1071+
# (the assert is executed only if ._tstate_lock is None).
10861072
#
1087-
# Special case: _main_thread releases ._running_lock via this
1073+
# Special case: _main_thread releases ._tstate_lock via this
10881074
# module's _shutdown() function.
1089-
lock = self._running_lock
1075+
lock = self._tstate_lock
10901076
if lock is not None:
10911077
assert not lock.locked()
10921078
self._is_stopped = True
1093-
self._running_lock = None
1079+
self._tstate_lock = None
10941080
if not self.daemon:
10951081
with _shutdown_locks_lock:
10961082
# Remove our lock and other released locks from _shutdown_locks
@@ -1137,17 +1123,20 @@ def join(self, timeout=None):
11371123
raise RuntimeError("cannot join current thread")
11381124

11391125
if timeout is None:
1140-
self._wait_for_running_lock()
1126+
self._wait_for_tstate_lock()
11411127
else:
11421128
# the behavior of a negative timeout isn't documented, but
11431129
# historically .join(timeout=x) for x<0 has acted as if timeout=0
1144-
self._wait_for_running_lock(timeout=max(timeout, 0))
1145-
1146-
def _wait_for_running_lock(self, block=True, timeout=-1):
1147-
# This method passes its arguments to _running_lock.acquire().
1148-
# If the lock is acquired, the python code is done, and self._stop() is
1149-
# called. That sets ._is_stopped to True, and ._running_lock to None.
1150-
lock = self._running_lock
1130+
self._wait_for_tstate_lock(timeout=max(timeout, 0))
1131+
1132+
def _wait_for_tstate_lock(self, block=True, timeout=-1):
1133+
# Issue #18808: wait for the thread state to be gone.
1134+
# At the end of the thread's life, after all knowledge of the thread
1135+
# is removed from C data structures, C code releases our _tstate_lock.
1136+
# This method passes its arguments to _tstate_lock.acquire().
1137+
# If the lock is acquired, the C code is done, and self._stop() is
1138+
# called. That sets ._is_stopped to True, and ._tstate_lock to None.
1139+
lock = self._tstate_lock
11511140
if lock is None:
11521141
# already determined that the C code is done
11531142
assert self._is_stopped
@@ -1218,7 +1207,7 @@ def is_alive(self):
12181207
assert self._initialized, "Thread.__init__() not called"
12191208
if self._is_stopped or not self._started.is_set():
12201209
return False
1221-
self._wait_for_running_lock(False)
1210+
self._wait_for_tstate_lock(False)
12221211
return not self._is_stopped
12231212

12241213
@property
@@ -1428,7 +1417,7 @@ class _MainThread(Thread):
14281417

14291418
def __init__(self):
14301419
Thread.__init__(self, name="MainThread", daemon=False)
1431-
self._set_running_lock()
1420+
self._set_tstate_lock()
14321421
self._started.set()
14331422
self._set_ident()
14341423
if _HAVE_THREAD_NATIVE_ID:
@@ -1569,7 +1558,7 @@ def _shutdown():
15691558
# dubious, but some code does it. We can't wait for C code to release
15701559
# the main thread's tstate_lock - that won't happen until the interpreter
15711560
# is nearly dead. So we release it here. Note that just calling _stop()
1572-
# isn't enough: other threads may already be waiting on _running_lock.
1561+
# isn't enough: other threads may already be waiting on _tstate_lock.
15731562
if _main_thread._is_stopped:
15741563
# _shutdown() was already called
15751564
return
@@ -1584,13 +1573,12 @@ def _shutdown():
15841573

15851574
# Main thread
15861575
if _main_thread.ident == get_ident():
1587-
assert _main_thread._tstate_lock is None
1588-
running_lock = _main_thread._running_lock
1589-
# The main thread isn't finished yet, so its running lock can't
1576+
tlock = _main_thread._tstate_lock
1577+
# The main thread isn't finished yet, so its thread state lock can't
15901578
# have been released.
1591-
assert running_lock is not None
1592-
assert running_lock.locked()
1593-
running_lock.release()
1579+
assert tlock is not None
1580+
assert tlock.locked()
1581+
tlock.release()
15941582
_main_thread._stop()
15951583
else:
15961584
# bpo-1596321: _shutdown() must be called in the main thread.

0 commit comments

Comments
 (0)