https://github.com/python/cpython/commit/9e7340cd3b5531784291088b504882cfb4d4c78c
commit: 9e7340cd3b5531784291088b504882cfb4d4c78c
branch: main
author: J Berg <[email protected]>
committer: gpshead <[email protected]>
date: 2025-11-11T22:09:58Z
summary:

gh-139462: Make the ProcessPoolExecutor BrokenProcessPool exception report 
which child process terminated (GH-139486)

Report which process terminated as cause of BPE

files:
A Misc/NEWS.d/next/Library/2025-10-02-22-29-00.gh-issue-139462.VZXUHe.rst
M Doc/whatsnew/3.15.rst
M Lib/concurrent/futures/process.py
M Lib/test/test_concurrent_futures/test_process_pool.py

diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst
index ecab0d03e105e6..c543b6e6c2a779 100644
--- a/Doc/whatsnew/3.15.rst
+++ b/Doc/whatsnew/3.15.rst
@@ -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
 -----------
 
diff --git a/Lib/concurrent/futures/process.py 
b/Lib/concurrent/futures/process.py
index a14650bf5fa47c..a42afa68efcb14 100644
--- a/Lib/concurrent/futures/process.py
+++ b/Lib/concurrent/futures/process.py
@@ -474,9 +474,23 @@ def _terminate_broken(self, cause):
         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():
diff --git a/Lib/test/test_concurrent_futures/test_process_pool.py 
b/Lib/test/test_concurrent_futures/test_process_pool.py
index 9685f980119a0e..731419a48bd128 100644
--- a/Lib/test/test_concurrent_futures/test_process_pool.py
+++ b/Lib/test/test_concurrent_futures/test_process_pool.py
@@ -106,6 +106,21 @@ def test_traceback(self):
         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
index 00000000000000..390a6124386151
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-10-02-22-29-00.gh-issue-139462.VZXUHe.rst
@@ -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.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to