]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-139462: Make the ProcessPoolExecutor BrokenProcessPool exception report which...
authorJ Berg <j.berg2349@gmail.com>
Tue, 11 Nov 2025 22:09:58 +0000 (22:09 +0000)
committerGitHub <noreply@github.com>
Tue, 11 Nov 2025 22:09:58 +0000 (22:09 +0000)
Report which process terminated as cause of BPE

Doc/whatsnew/3.15.rst
Lib/concurrent/futures/process.py
Lib/test/test_concurrent_futures/test_process_pool.py
Misc/NEWS.d/next/Library/2025-10-02-22-29-00.gh-issue-139462.VZXUHe.rst [new file with mode: 0644]

index ecab0d03e105e69687f7c0064e71fab951e87734..c543b6e6c2a7794242f9fbd63211e140401d0455 100644 (file)
@@ -369,6 +369,16 @@ collections.abc
   :mod:`!collections.abc` module.
 
 
+concurrent.futures
+------------------
+
+* Improved error reporting when a child process in a
+  :class:`concurrent.futures.ProcessPoolExecutor` terminates abruptly.
+  The resulting traceback will now tell you the PID and exit code of the
+  terminated process.
+  (Contributed by Jonathan Berg in :gh:`139486`.)
+
+
 dataclasses
 -----------
 
index a14650bf5fa47cd65037b078a4e6572ace4480c5..a42afa68efcb1484df9f63d9544b855ac8b3a03e 100644 (file)
@@ -474,9 +474,23 @@ class _ExecutorManagerThread(threading.Thread):
         bpe = BrokenProcessPool("A process in the process pool was "
                                 "terminated abruptly while the future was "
                                 "running or pending.")
+        cause_str = None
         if cause is not None:
-            bpe.__cause__ = _RemoteTraceback(
-                f"\n'''\n{''.join(cause)}'''")
+            cause_str = ''.join(cause)
+        else:
+            # No cause known, so report any processes that have
+            # terminated with nonzero exit codes, e.g. from a
+            # segfault. Multiple may terminate simultaneously,
+            # so include all of them in the traceback.
+            errors = []
+            for p in self.processes.values():
+                if p.exitcode is not None and p.exitcode != 0:
+                    errors.append(f"Process {p.pid} terminated abruptly "
+                                  f"with exit code {p.exitcode}")
+            if errors:
+                cause_str = "\n".join(errors)
+        if cause_str:
+            bpe.__cause__ = _RemoteTraceback(f"\n'''\n{cause_str}'''")
 
         # Mark pending tasks as failed.
         for work_id, work_item in self.pending_work_items.items():
index 9685f980119a0e8a729136a5524df2dce6015cd6..731419a48bd1281e9b36d543beed1a0a734e1922 100644 (file)
@@ -106,6 +106,21 @@ class ProcessPoolExecutorTest(ExecutorTest):
         self.assertIn('raise RuntimeError(123) # some comment',
                       f1.getvalue())
 
+    def test_traceback_when_child_process_terminates_abruptly(self):
+        # gh-139462 enhancement - BrokenProcessPool exceptions
+        # should describe which process terminated.
+        exit_code = 99
+        with self.executor_type(max_workers=1) as executor:
+            future = executor.submit(os._exit, exit_code)
+            with self.assertRaises(BrokenProcessPool) as bpe:
+                future.result()
+
+        cause = bpe.exception.__cause__
+        self.assertIsInstance(cause, futures.process._RemoteTraceback)
+        self.assertIn(
+            f"terminated abruptly with exit code {exit_code}", cause.tb
+        )
+
     @warnings_helper.ignore_fork_in_thread_deprecation_warnings()
     @hashlib_helper.requires_hashdigest('md5')
     def test_ressources_gced_in_workers(self):
diff --git a/Misc/NEWS.d/next/Library/2025-10-02-22-29-00.gh-issue-139462.VZXUHe.rst b/Misc/NEWS.d/next/Library/2025-10-02-22-29-00.gh-issue-139462.VZXUHe.rst
new file mode 100644 (file)
index 0000000..390a612
--- /dev/null
@@ -0,0 +1,3 @@
+When a child process in a :class:`concurrent.futures.ProcessPoolExecutor`
+terminates abruptly, the resulting traceback will now tell you the PID
+and exit code of the terminated process. Contributed by Jonathan Berg.