Skip to content

Commit

Permalink
bpo-31804: Fix multiprocessing.Process with broken standard streams (G…
Browse files Browse the repository at this point in the history
…H-6079) (GH-6080)

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)

Co-authored-by: Antoine Pitrou <pitrou@free.fr>
  • Loading branch information
miss-islington and pitrou committed Mar 11, 2018
1 parent 04aadf2 commit ff5d213
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 12 deletions.
9 changes: 1 addition & 8 deletions Lib/multiprocessing/popen_fork.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 1 addition & 2 deletions Lib/multiprocessing/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,8 +314,7 @@ def _bootstrap(self):
finally:
threading._shutdown()
util.info('process exiting with exitcode %d' % exitcode)
sys.stdout.flush()
sys.stderr.flush()
util._flush_std_streams()

return exitcode

Expand Down
14 changes: 14 additions & 0 deletions Lib/multiprocessing/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
#
Expand Down
31 changes: 29 additions & 2 deletions Lib/test/_test_multiprocessing.py
Original file line number Diff line number Diff line change
Expand Up @@ -584,10 +584,19 @@ def test_wait_for_threads(self):
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'):
Expand All @@ -601,6 +610,24 @@ def test_error_on_stdio_flush(self):
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)

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

0 comments on commit ff5d213

Please sign in to comment.