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

Report which process terminated as cause of BPE
This commit is contained in:
J Berg
2025-11-11 22:09:58 +00:00
committed by GitHub
parent 7906f4d96a
commit 9e7340cd3b
4 changed files with 44 additions and 2 deletions

View 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
-----------

View 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():

View 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):

View File

@@ -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.