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:
@@ -369,6 +369,16 @@ collections.abc
|
|||||||
:mod:`!collections.abc` module.
|
: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
|
dataclasses
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
|||||||
@@ -474,9 +474,23 @@ class _ExecutorManagerThread(threading.Thread):
|
|||||||
bpe = BrokenProcessPool("A process in the process pool was "
|
bpe = BrokenProcessPool("A process in the process pool was "
|
||||||
"terminated abruptly while the future was "
|
"terminated abruptly while the future was "
|
||||||
"running or pending.")
|
"running or pending.")
|
||||||
|
cause_str = None
|
||||||
if cause is not None:
|
if cause is not None:
|
||||||
bpe.__cause__ = _RemoteTraceback(
|
cause_str = ''.join(cause)
|
||||||
f"\n'''\n{''.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.
|
# Mark pending tasks as failed.
|
||||||
for work_id, work_item in self.pending_work_items.items():
|
for work_id, work_item in self.pending_work_items.items():
|
||||||
|
|||||||
@@ -106,6 +106,21 @@ class ProcessPoolExecutorTest(ExecutorTest):
|
|||||||
self.assertIn('raise RuntimeError(123) # some comment',
|
self.assertIn('raise RuntimeError(123) # some comment',
|
||||||
f1.getvalue())
|
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()
|
@warnings_helper.ignore_fork_in_thread_deprecation_warnings()
|
||||||
@hashlib_helper.requires_hashdigest('md5')
|
@hashlib_helper.requires_hashdigest('md5')
|
||||||
def test_ressources_gced_in_workers(self):
|
def test_ressources_gced_in_workers(self):
|
||||||
|
|||||||
@@ -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.
|
||||||
Reference in New Issue
Block a user