Skip to content

Commit feab7c4

Browse files
committed
[3.6] bpo-31804: Fix multiprocessing.Process with broken standard streams (pythonGH-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.. (cherry picked from commit e756f66)
1 parent 20ac11a commit feab7c4

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._launch(process_obj)
2720

Lib/multiprocessing/process.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -274,8 +274,7 @@ def _bootstrap(self):
274274
traceback.print_exc()
275275
finally:
276276
util.info('process exiting with exitcode %d' % exitcode)
277-
sys.stdout.flush()
278-
sys.stderr.flush()
277+
util._flush_std_streams()
279278

280279
return exitcode
281280

Lib/multiprocessing/util.py

+14
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,20 @@ def _close_stdin():
388388
except (OSError, ValueError):
389389
pass
390390

391+
#
392+
# Flush standard streams, if any
393+
#
394+
395+
def _flush_std_streams():
396+
try:
397+
sys.stdout.flush()
398+
except (AttributeError, ValueError):
399+
pass
400+
try:
401+
sys.stderr.flush()
402+
except (AttributeError, ValueError):
403+
pass
404+
391405
#
392406
# Start a program with only specified fds kept open
393407
#

Lib/test/_test_multiprocessing.py

+29-2
Original file line numberDiff line numberDiff line change
@@ -427,10 +427,19 @@ def test_lose_target_ref(self):
427427
close_queue(q)
428428

429429
@classmethod
430-
def _test_error_on_stdio_flush(self, evt):
430+
def _test_error_on_stdio_flush(self, evt, break_std_streams={}):
431+
for stream_name, action in break_std_streams.items():
432+
if action == 'close':
433+
stream = io.StringIO()
434+
stream.close()
435+
else:
436+
assert action == 'remove'
437+
stream = None
438+
setattr(sys, stream_name, None)
431439
evt.set()
432440

433-
def test_error_on_stdio_flush(self):
441+
def test_error_on_stdio_flush_1(self):
442+
# Check that Process works with broken standard streams
434443
streams = [io.StringIO(), None]
435444
streams[0].close()
436445
for stream_name in ('stdout', 'stderr'):
@@ -444,6 +453,24 @@ def test_error_on_stdio_flush(self):
444453
proc.start()
445454
proc.join()
446455
self.assertTrue(evt.is_set())
456+
self.assertEqual(proc.exitcode, 0)
457+
finally:
458+
setattr(sys, stream_name, old_stream)
459+
460+
def test_error_on_stdio_flush_2(self):
461+
# Same as test_error_on_stdio_flush_1(), but standard streams are
462+
# broken by the child process
463+
for stream_name in ('stdout', 'stderr'):
464+
for action in ('close', 'remove'):
465+
old_stream = getattr(sys, stream_name)
466+
try:
467+
evt = self.Event()
468+
proc = self.Process(target=self._test_error_on_stdio_flush,
469+
args=(evt, {stream_name: action}))
470+
proc.start()
471+
proc.join()
472+
self.assertTrue(evt.is_set())
473+
self.assertEqual(proc.exitcode, 0)
447474
finally:
448475
setattr(sys, stream_name, old_stream)
449476

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)