Skip to content

Commit e756f66

Browse files
authored
bpo-31804: Fix multiprocessing.Process with broken standard streams (python#6079)
In some conditions the standard streams will be None or closed in the child process (for example if using "pythonw" instead of "python" on Windows). Avoid failing with a non-0 exit code in those conditions. Report and initial patch by poxthegreat.
1 parent 9fb8415 commit e756f66

File tree

5 files changed

+47
-12
lines changed

5 files changed

+47
-12
lines changed

Lib/multiprocessing/popen_fork.py

+1-8
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,7 @@ class Popen(object):
1414
method = 'fork'
1515

1616
def __init__(self, process_obj):
17-
try:
18-
sys.stdout.flush()
19-
except (AttributeError, ValueError):
20-
pass
21-
try:
22-
sys.stderr.flush()
23-
except (AttributeError, ValueError):
24-
pass
17+
util._flush_std_streams()
2518
self.returncode = None
2619
self.finalizer = None
2720
self._launch(process_obj)

Lib/multiprocessing/process.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -314,8 +314,7 @@ def _bootstrap(self):
314314
finally:
315315
threading._shutdown()
316316
util.info('process exiting with exitcode %d' % exitcode)
317-
sys.stdout.flush()
318-
sys.stderr.flush()
317+
util._flush_std_streams()
319318

320319
return exitcode
321320

Lib/multiprocessing/util.py

+14
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,20 @@ def _close_stdin():
391391
except (OSError, ValueError):
392392
pass
393393

394+
#
395+
# Flush standard streams, if any
396+
#
397+
398+
def _flush_std_streams():
399+
try:
400+
sys.stdout.flush()
401+
except (AttributeError, ValueError):
402+
pass
403+
try:
404+
sys.stderr.flush()
405+
except (AttributeError, ValueError):
406+
pass
407+
394408
#
395409
# Start a program with only specified fds kept open
396410
#

Lib/test/_test_multiprocessing.py

+29-2
Original file line numberDiff line numberDiff line change
@@ -584,10 +584,19 @@ def test_wait_for_threads(self):
584584
self.assertTrue(evt.is_set())
585585

586586
@classmethod
587-
def _test_error_on_stdio_flush(self, evt):
587+
def _test_error_on_stdio_flush(self, evt, break_std_streams={}):
588+
for stream_name, action in break_std_streams.items():
589+
if action == 'close':
590+
stream = io.StringIO()
591+
stream.close()
592+
else:
593+
assert action == 'remove'
594+
stream = None
595+
setattr(sys, stream_name, None)
588596
evt.set()
589597

590-
def test_error_on_stdio_flush(self):
598+
def test_error_on_stdio_flush_1(self):
599+
# Check that Process works with broken standard streams
591600
streams = [io.StringIO(), None]
592601
streams[0].close()
593602
for stream_name in ('stdout', 'stderr'):
@@ -601,6 +610,24 @@ def test_error_on_stdio_flush(self):
601610
proc.start()
602611
proc.join()
603612
self.assertTrue(evt.is_set())
613+
self.assertEqual(proc.exitcode, 0)
614+
finally:
615+
setattr(sys, stream_name, old_stream)
616+
617+
def test_error_on_stdio_flush_2(self):
618+
# Same as test_error_on_stdio_flush_1(), but standard streams are
619+
# broken by the child process
620+
for stream_name in ('stdout', 'stderr'):
621+
for action in ('close', 'remove'):
622+
old_stream = getattr(sys, stream_name)
623+
try:
624+
evt = self.Event()
625+
proc = self.Process(target=self._test_error_on_stdio_flush,
626+
args=(evt, {stream_name: action}))
627+
proc.start()
628+
proc.join()
629+
self.assertTrue(evt.is_set())
630+
self.assertEqual(proc.exitcode, 0)
604631
finally:
605632
setattr(sys, stream_name, old_stream)
606633

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Avoid failing in multiprocessing.Process if the standard streams are closed
2+
or None at exit.

0 commit comments

Comments
 (0)