Issue 18984: Remove ._stopped Event from Thread internals.

The fix for issue 18808 left us checking two things to be sure a Thread
was done:  an Event (._stopped) and a mutex (._tstate_lock).  Clumsy &
brittle.  This patch removes the Event, leaving just a happy lock :-)

The bulk of the patch removes two excruciating tests, which were
verifying sanity of the internals of the ._stopped Event after a fork.
Thanks to Antoine Pitrou for verifying that's the only real value
these tests had.

One consequence of moving from an Event to a mutex:  waiters (threads
calling Thread.join()) used to block each on their own unique mutex
(internal to the ._stopped event), but now all contend on the same
mutex (._tstate_lock).  These approaches have different performance
characteristics on different platforms.  I don't think it matters in
this context.
This commit is contained in:
Tim Peters 2013-09-08 18:44:40 -05:00
parent 050b62d1a6
commit c363a23eff
2 changed files with 30 additions and 167 deletions

View file

@ -549,7 +549,7 @@ class Thread:
self._ident = None
self._tstate_lock = None
self._started = Event()
self._stopped = Event()
self._is_stopped = False
self._initialized = True
# sys.stderr is not stored in the class like
# sys.exc_info since it can be changed between instances
@ -561,7 +561,6 @@ class Thread:
# private! Called by _after_fork() to reset our internal locks as
# they may be in an invalid state leading to a deadlock or crash.
self._started._reset_internal_locks()
self._stopped._reset_internal_locks()
if is_alive:
self._set_tstate_lock()
else:
@ -574,7 +573,7 @@ class Thread:
status = "initial"
if self._started.is_set():
status = "started"
if self._stopped.is_set():
if self._is_stopped:
status = "stopped"
if self._daemonic:
status += " daemon"
@ -696,7 +695,6 @@ class Thread:
pass
finally:
with _active_limbo_lock:
self._stop()
try:
# We don't call self._delete() because it also
# grabs _active_limbo_lock.
@ -705,7 +703,8 @@ class Thread:
pass
def _stop(self):
self._stopped.set()
self._is_stopped = True
self._tstate_lock = None
def _delete(self):
"Remove current thread from the dict of currently running threads."
@ -749,29 +748,24 @@ class Thread:
raise RuntimeError("cannot join thread before it is started")
if self is current_thread():
raise RuntimeError("cannot join current thread")
if not self.is_alive():
return
self._stopped.wait(timeout)
if self._stopped.is_set():
self._wait_for_tstate_lock(timeout is None)
if timeout is None:
self._wait_for_tstate_lock()
else:
self._wait_for_tstate_lock(timeout=timeout)
def _wait_for_tstate_lock(self, block):
def _wait_for_tstate_lock(self, block=True, timeout=-1):
# Issue #18808: wait for the thread state to be gone.
# When self._stopped is set, the Python part of the thread is done,
# but the thread's tstate has not yet been destroyed. The C code
# releases self._tstate_lock when the C part of the thread is done
# (the code at the end of the thread's life to remove all knowledge
# of the thread from the C data structures).
# This method waits to acquire _tstate_lock if `block` is True, or
# sees whether it can be acquired immediately if `block` is False.
# If it does acquire the lock, the C code is done, and _tstate_lock
# is set to None.
# At the end of the thread's life, after all knowledge of the thread
# is removed from C data structures, C code releases our _tstate_lock.
# This method passes its arguments to _tstate_lock.aquire().
# If the lock is acquired, the C code is done, and self._stop() is
# called. That sets ._is_stopped to True, and ._tstate_lock to None.
lock = self._tstate_lock
if lock is None:
return # already determined that the C code is done
if lock.acquire(block):
if lock is None: # already determined that the C code is done
assert self._is_stopped
elif lock.acquire(block, timeout):
lock.release()
self._tstate_lock = None
self._stop()
@property
def name(self):
@ -790,14 +784,10 @@ class Thread:
def is_alive(self):
assert self._initialized, "Thread.__init__() not called"
if not self._started.is_set():
if self._is_stopped or not self._started.is_set():
return False
if not self._stopped.is_set():
return True
# The Python part of the thread is done, but the C part may still be
# waiting to run.
self._wait_for_tstate_lock(False)
return self._tstate_lock is not None
return not self._is_stopped
isAlive = is_alive
@ -861,6 +851,7 @@ class _MainThread(Thread):
def __init__(self):
Thread.__init__(self, name="MainThread", daemon=False)
self._set_tstate_lock()
self._started.set()
self._set_ident()
with _active_limbo_lock:
@ -925,6 +916,14 @@ from _thread import stack_size
_main_thread = _MainThread()
def _shutdown():
# Obscure: other threads may be waiting to join _main_thread. That's
# dubious, but some code does it. We can't wait for C code to release
# the main thread's tstate_lock - that won't happen until the interpreter
# is nearly dead. So we release it here. Note that just calling _stop()
# isn't enough: other threads may already be waiting on _tstate_lock.
assert _main_thread._tstate_lock is not None
assert _main_thread._tstate_lock.locked()
_main_thread._tstate_lock.release()
_main_thread._stop()
t = _pickSomeNonDaemonThread()
while t: