Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,15 @@ collections.abc
previously emitted if it was merely imported or accessed from the
: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`.)

dbm
---

Expand Down
15 changes: 13 additions & 2 deletions Lib/concurrent/futures/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -474,9 +474,20 @@ 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, synthesize from child process exitcodes
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():
Expand Down
21 changes: 21 additions & 0 deletions Lib/test/test_concurrent_futures/test_process_pool.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,27 @@ def test_traceback(self):
self.assertIn('raise RuntimeError(123) # some comment',
f1.getvalue())

@staticmethod
def _terminate_abruptly_with_exit_code(exit_code):
os._exit(exit_code)

def test_traceback_when_child_process_terminates_abruptly(self):
# gh-139462 enhancement - BrokenProcessPool exceptions
# should describe which process terminated.
exit_code = 99
future = self.executor.submit(
self._terminate_abruptly_with_exit_code,
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):
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Loading