3333# Rename some stuff so "from threading import *" is safe
3434_start_new_thread = _thread .start_new_thread
3535_allocate_lock = _thread .allocate_lock
36+ _set_sentinel = _thread ._set_sentinel
3637get_ident = _thread .get_ident
3738ThreadError = _thread .error
3839try :
@@ -548,28 +549,33 @@ def __init__(self, group=None, name=None,
548549 else :
549550 self ._daemonic = current_thread ().daemon
550551 self ._ident = None
552+ self ._tstate_lock = None
551553 self ._started = Event ()
552- self ._stopped = False
553- self ._block = Condition (Lock ())
554+ self ._stopped = Event ()
554555 self ._initialized = True
555556 # sys.stderr is not stored in the class like
556557 # sys.exc_info since it can be changed between instances
557558 self ._stderr = _sys .stderr
558559 _dangling .add (self )
559560
560- def _reset_internal_locks (self ):
561+ def _reset_internal_locks (self , is_alive ):
561562 # private! Called by _after_fork() to reset our internal locks as
562563 # they may be in an invalid state leading to a deadlock or crash.
563- if hasattr (self , '_block' ): # DummyThread deletes _block
564- self ._block .__init__ ()
565564 self ._started ._reset_internal_locks ()
565+ self ._stopped ._reset_internal_locks ()
566+ if is_alive :
567+ self ._set_tstate_lock ()
568+ else :
569+ # The thread isn't alive after fork: it doesn't have a tstate
570+ # anymore.
571+ self ._tstate_lock = None
566572
567573 def __repr__ (self ):
568574 assert self ._initialized , "Thread.__init__() was not called"
569575 status = "initial"
570576 if self ._started .is_set ():
571577 status = "started"
572- if self ._stopped :
578+ if self ._stopped . is_set () :
573579 status = "stopped"
574580 if self ._daemonic :
575581 status += " daemon"
@@ -625,9 +631,18 @@ def _bootstrap(self):
625631 def _set_ident (self ):
626632 self ._ident = get_ident ()
627633
634+ def _set_tstate_lock (self ):
635+ """
636+ Set a lock object which will be released by the interpreter when
637+ the underlying thread state (see pystate.h) gets deleted.
638+ """
639+ self ._tstate_lock = _set_sentinel ()
640+ self ._tstate_lock .acquire ()
641+
628642 def _bootstrap_inner (self ):
629643 try :
630644 self ._set_ident ()
645+ self ._set_tstate_lock ()
631646 self ._started .set ()
632647 with _active_limbo_lock :
633648 _active [self ._ident ] = self
@@ -691,10 +706,7 @@ def _bootstrap_inner(self):
691706 pass
692707
693708 def _stop (self ):
694- self ._block .acquire ()
695- self ._stopped = True
696- self ._block .notify_all ()
697- self ._block .release ()
709+ self ._stopped .set ()
698710
699711 def _delete (self ):
700712 "Remove current thread from the dict of currently running threads."
@@ -738,21 +750,29 @@ def join(self, timeout=None):
738750 raise RuntimeError ("cannot join thread before it is started" )
739751 if self is current_thread ():
740752 raise RuntimeError ("cannot join current thread" )
741-
742- self ._block .acquire ()
743- try :
744- if timeout is None :
745- while not self ._stopped :
746- self ._block .wait ()
747- else :
748- deadline = _time () + timeout
749- while not self ._stopped :
750- delay = deadline - _time ()
751- if delay <= 0 :
752- break
753- self ._block .wait (delay )
754- finally :
755- self ._block .release ()
753+ if not self .is_alive ():
754+ return
755+ self ._stopped .wait (timeout )
756+ if self ._stopped .is_set ():
757+ self ._wait_for_tstate_lock (timeout is None )
758+
759+ def _wait_for_tstate_lock (self , block ):
760+ # Issue #18808: wait for the thread state to be gone.
761+ # When self._stopped is set, the Python part of the thread is done,
762+ # but the thread's tstate has not yet been destroyed. The C code
763+ # releases self._tstate_lock when the C part of the thread is done
764+ # (the code at the end of the thread's life to remove all knowledge
765+ # of the thread from the C data structures).
766+ # This method waits to acquire _tstate_lock if `block` is True, or
767+ # sees whether it can be acquired immediately if `block` is False.
768+ # If it does acquire the lock, the C code is done, and _tstate_lock
769+ # is set to None.
770+ lock = self ._tstate_lock
771+ if lock is None :
772+ return # already determined that the C code is done
773+ if lock .acquire (block ):
774+ lock .release ()
775+ self ._tstate_lock = None
756776
757777 @property
758778 def name (self ):
@@ -771,7 +791,14 @@ def ident(self):
771791
772792 def is_alive (self ):
773793 assert self ._initialized , "Thread.__init__() not called"
774- return self ._started .is_set () and not self ._stopped
794+ if not self ._started .is_set ():
795+ return False
796+ if not self ._stopped .is_set ():
797+ return True
798+ # The Python part of the thread is done, but the C part may still be
799+ # waiting to run.
800+ self ._wait_for_tstate_lock (False )
801+ return self ._tstate_lock is not None
775802
776803 isAlive = is_alive
777804
@@ -854,11 +881,6 @@ class _DummyThread(Thread):
854881 def __init__ (self ):
855882 Thread .__init__ (self , name = _newname ("Dummy-%d" ), daemon = True )
856883
857- # Thread._block consumes an OS-level locking primitive, which
858- # can never be used by a _DummyThread. Since a _DummyThread
859- # instance is immortal, that's bad, so release this resource.
860- del self ._block
861-
862884 self ._started .set ()
863885 self ._set_ident ()
864886 with _active_limbo_lock :
@@ -952,15 +974,16 @@ def _after_fork():
952974 for thread in _enumerate ():
953975 # Any lock/condition variable may be currently locked or in an
954976 # invalid state, so we reinitialize them.
955- thread ._reset_internal_locks ()
956977 if thread is current :
957978 # There is only one active thread. We reset the ident to
958979 # its new value since it can have changed.
980+ thread ._reset_internal_locks (True )
959981 ident = get_ident ()
960982 thread ._ident = ident
961983 new_active [ident ] = thread
962984 else :
963985 # All the others are already stopped.
986+ thread ._reset_internal_locks (False )
964987 thread ._stop ()
965988
966989 _limbo .clear ()
0 commit comments