]> granicus.if.org Git - python/commitdiff
bpo-18966: non-daemonic threads created by a multiprocessing.Process should be joined...
authorAntoine Pitrou <pitrou@free.fr>
Wed, 16 Aug 2017 18:53:28 +0000 (20:53 +0200)
committerGitHub <noreply@github.com>
Wed, 16 Aug 2017 18:53:28 +0000 (20:53 +0200)
* bpo-18966: non-daemonic threads created by a multiprocessing.Process should be joined on exit

* Add NEWS blurb

Lib/multiprocessing/process.py
Lib/test/_test_multiprocessing.py
Lib/threading.py
Misc/NEWS.d/next/Library/2017-08-16-20-28-06.bpo-18966.mjHWk2.rst [new file with mode: 0644]

index 8e500dc93dc6bdbc3c228b9dd851ff98ea83f0d8..8fff3e105ead4cf4e0196263a36d0978bd288bee 100644 (file)
@@ -17,6 +17,7 @@ import os
 import sys
 import signal
 import itertools
+import threading
 from _weakrefset import WeakSet
 
 #
@@ -311,6 +312,7 @@ class BaseProcess(object):
             sys.stderr.write('Process %s:\n' % self.name)
             traceback.print_exc()
         finally:
+            threading._shutdown()
             util.info('process exiting with exitcode %d' % exitcode)
             sys.stdout.flush()
             sys.stderr.flush()
index dce62df1d0234914c63b60df25ab77310b01503b..d6fe7d62675631d2f62d73ffc9f928860fa8fbba 100644 (file)
@@ -542,6 +542,32 @@ class _TestProcess(BaseTestCase):
                 p.join()
             close_queue(q)
 
+    @classmethod
+    def _test_wait_for_threads(self, evt):
+        def func1():
+            time.sleep(0.5)
+            evt.set()
+
+        def func2():
+            time.sleep(20)
+            evt.clear()
+
+        threading.Thread(target=func1).start()
+        threading.Thread(target=func2, daemon=True).start()
+
+    def test_wait_for_threads(self):
+        # A child process should wait for non-daemonic threads to end
+        # before exiting
+        if self.TYPE == 'threads':
+            self.skipTest('test not appropriate for {}'.format(self.TYPE))
+
+        evt = self.Event()
+        proc = self.Process(target=self._test_wait_for_threads, args=(evt,))
+        proc.start()
+        proc.join()
+        self.assertTrue(evt.is_set())
+
+
 #
 #
 #
index 92c2ab365b4f9126e0f005bfc3df60181829b194..06dbc6828347d2fdd4ef87985786dccace77b958 100644 (file)
@@ -1284,6 +1284,9 @@ 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.
+    if _main_thread._is_stopped:
+        # _shutdown() was already called
+        return
     tlock = _main_thread._tstate_lock
     # The main thread isn't finished yet, so its thread state lock can't have
     # been released.
diff --git a/Misc/NEWS.d/next/Library/2017-08-16-20-28-06.bpo-18966.mjHWk2.rst b/Misc/NEWS.d/next/Library/2017-08-16-20-28-06.bpo-18966.mjHWk2.rst
new file mode 100644 (file)
index 0000000..53bea71
--- /dev/null
@@ -0,0 +1,2 @@
+Non-daemonic threads created by a multiprocessing.Process are now joined on
+child exit.