[3.6] bpo-31804: Fix multiprocessing.Process with broken standard streams (GH-6079) (GH-6081)

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 e756f66c83)
This commit is contained in:
Antoine Pitrou 2018-03-11 20:09:20 +01:00 committed by GitHub
parent 20ac11a9fb
commit 069b8d20be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 47 additions and 12 deletions

View File

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

View File

@ -274,8 +274,7 @@ class BaseProcess(object):
traceback.print_exc()
finally:
util.info('process exiting with exitcode %d' % exitcode)
sys.stdout.flush()
sys.stderr.flush()
util._flush_std_streams()
return exitcode

View File

@ -388,6 +388,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
#

View File

@ -427,10 +427,19 @@ class _TestProcess(BaseTestCase):
close_queue(q)
@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'):
@ -444,6 +453,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)

View File

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