diff options
Diffstat (limited to 'Lib/threading.py')
| -rw-r--r-- | Lib/threading.py | 83 |
1 files changed, 53 insertions, 30 deletions
diff --git a/Lib/threading.py b/Lib/threading.py index b4b73a8a9c..26d1018d3e 100644 --- a/Lib/threading.py +++ b/Lib/threading.py @@ -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,12 +561,12 @@ 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: # The thread isn't alive after fork: it doesn't have a tstate # anymore. + self._is_stopped = True self._tstate_lock = None def __repr__(self): @@ -574,7 +574,8 @@ class Thread: status = "initial" if self._started.is_set(): status = "started" - if self._stopped.is_set(): + self.is_alive() # easy way to get ._is_stopped set when appropriate + if self._is_stopped: status = "stopped" if self._daemonic: status += " daemon" @@ -696,7 +697,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 +705,27 @@ class Thread: pass def _stop(self): - self._stopped.set() + # After calling ._stop(), .is_alive() returns False and .join() returns + # immediately. ._tstate_lock must be released before calling ._stop(). + # + # Normal case: C code at the end of the thread's life + # (release_sentinel in _threadmodule.c) releases ._tstate_lock, and + # that's detected by our ._wait_for_tstate_lock(), called by .join() + # and .is_alive(). Any number of threads _may_ call ._stop() + # simultaneously (for example, if multiple threads are blocked in + # .join() calls), and they're not serialized. That's harmless - + # they'll just make redundant rebindings of ._is_stopped and + # ._tstate_lock. Obscure: we rebind ._tstate_lock last so that the + # "assert self._is_stopped" in ._wait_for_tstate_lock() always works + # (the assert is executed only if ._tstate_lock is None). + # + # Special case: _main_thread releases ._tstate_lock via this + # module's _shutdown() function. + lock = self._tstate_lock + if lock is not None: + assert not lock.locked() + self._is_stopped = True + self._tstate_lock = None def _delete(self): "Remove current thread from the dict of currently running threads." @@ -749,29 +769,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 +805,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 +872,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 +937,17 @@ 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. + tlock = _main_thread._tstate_lock + # The main thread isn't finished yet, so its thread state lock can't have + # been released. + assert tlock is not None + assert tlock.locked() + tlock.release() _main_thread._stop() t = _pickSomeNonDaemonThread() while t: |
