Commit e756f66c authored by Antoine Pitrou's avatar Antoine Pitrou Committed by GitHub

bpo-31804: Fix multiprocessing.Process with broken standard streams (#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.
parent 9fb84157
......@@ -14,14 +14,7 @@ class Popen(object):
method = 'fork'
def __init__(self, process_obj):
try:
sys.stdout.flush()
except (AttributeError, ValueError):
pass
try:
sys.stderr.flush()
except (AttributeError, ValueError):
pass
util._flush_std_streams()
self.returncode = None
self.finalizer = None
self._launch(process_obj)
......
......@@ -314,8 +314,7 @@ class BaseProcess(object):
finally:
threading._shutdown()
util.info('process exiting with exitcode %d' % exitcode)
sys.stdout.flush()
sys.stderr.flush()
util._flush_std_streams()
return exitcode
......
......@@ -391,6 +391,20 @@ def _close_stdin():
except (OSError, ValueError):
pass
#
# Flush standard streams, if any
#
def _flush_std_streams():
try:
sys.stdout.flush()
except (AttributeError, ValueError):
pass
try:
sys.stderr.flush()
except (AttributeError, ValueError):
pass
#
# Start a program with only specified fds kept open
#
......
......@@ -584,10 +584,19 @@ class _TestProcess(BaseTestCase):
self.assertTrue(evt.is_set())
@classmethod
def _test_error_on_stdio_flush(self, evt):
def _test_error_on_stdio_flush(self, evt, break_std_streams={}):
for stream_name, action in break_std_streams.items():
if action == 'close':
stream = io.StringIO()
stream.close()
else:
assert action == 'remove'
stream = None
setattr(sys, stream_name, None)
evt.set()
def test_error_on_stdio_flush(self):
def test_error_on_stdio_flush_1(self):
# Check that Process works with broken standard streams
streams = [io.StringIO(), None]
streams[0].close()
for stream_name in ('stdout', 'stderr'):
......@@ -601,6 +610,24 @@ class _TestProcess(BaseTestCase):
proc.start()
proc.join()
self.assertTrue(evt.is_set())
self.assertEqual(proc.exitcode, 0)
finally:
setattr(sys, stream_name, old_stream)
def test_error_on_stdio_flush_2(self):
# Same as test_error_on_stdio_flush_1(), but standard streams are
# broken by the child process
for stream_name in ('stdout', 'stderr'):
for action in ('close', 'remove'):
old_stream = getattr(sys, stream_name)
try:
evt = self.Event()
proc = self.Process(target=self._test_error_on_stdio_flush,
args=(evt, {stream_name: action}))
proc.start()
proc.join()
self.assertTrue(evt.is_set())
self.assertEqual(proc.exitcode, 0)
finally:
setattr(sys, stream_name, old_stream)
......
Avoid failing in multiprocessing.Process if the standard streams are closed
or None at exit.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment