Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 Lib/concurrent/futures/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,15 @@ def _terminate_broken(self, cause):
if cause is not None:
bpe.__cause__ = _RemoteTraceback(
f"\n'''\n{''.join(cause)}'''")
else:
# No cause known, so try to report some helpful info about
# which process(es) terminated and with what exit code
errors = []
for p in self.processes.values():
if p.exitcode: # Report any nonzero exit codes
errors.append(f"Process {p.pid} terminated abruptly with exit code {p.exitcode}")
if errors:
bpe.__cause__ = _RemoteTraceback("\n".join(errors))

# Mark pending tasks as failed.
for work_id, work_item in self.pending_work_items.items():
Expand Down
18 changes: 18 additions & 0 deletions Lib/test/test_concurrent_futures/test_process_pool.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import ctypes
import os
import queue
import sys
Expand Down Expand Up @@ -106,6 +107,23 @@ def test_traceback(self):
self.assertIn('raise RuntimeError(123) # some comment',
f1.getvalue())

@staticmethod
def _segfault():
ctypes.string_at(0)

def test_traceback_when_child_process_segfaults(self):
# gh-139462 enhancement - BrokenProcessPool exceptions
# should describe which process terminated.
future = self.executor.submit(self._segfault)
with self.assertRaises(Exception) as cm:
future.result()

bpe = cm.exception
self.assertIs(type(bpe), BrokenProcessPool)
cause = bpe.__cause__
self.assertIs(type(cause), futures.process._RemoteTraceback)
self.assertIn("terminated abruptly with 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
Loading