]> granicus.if.org Git - python/commitdiff
Another stab at the thread cleanup patch.
authorTim Peters <tim@python.org>
Mon, 9 Sep 2013 19:41:50 +0000 (14:41 -0500)
committerTim Peters <tim@python.org>
Mon, 9 Sep 2013 19:41:50 +0000 (14:41 -0500)
Antoine Pitrou found a variation that worked for him on the
thread+fork tests, and added an important

    self._is_stopped = True

to the after-fork code.  I confess I don't know why things passed
before.  But then mixing fork with threads is insane ;-)

Lib/threading.py

index 1921ee34cd53a815198e9005e363806fa43b1933..1ad22a430c8a1749862b419b813e7720906a8564 100644 (file)
@@ -566,6 +566,7 @@ class Thread:
         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):
@@ -703,6 +704,25 @@ class Thread:
                     pass
 
     def _stop(self):
+        # 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
 
@@ -921,9 +941,12 @@ def _shutdown():
     # 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()
+    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: