summaryrefslogtreecommitdiff
path: root/Lib/threading.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/threading.py')
-rw-r--r--Lib/threading.py83
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: