diff --git a/Lib/multiprocessing/process.py b/Lib/multiprocessing/process.py index 8fff3e105ead4cf..ac2eb5676f16d7d 100644 --- a/Lib/multiprocessing/process.py +++ b/Lib/multiprocessing/process.py @@ -314,8 +314,14 @@ def _bootstrap(self): finally: threading._shutdown() util.info('process exiting with exitcode %d' % exitcode) - sys.stdout.flush() - sys.stderr.flush() + try: + sys.stdout.flush() + except (AttributeError, ValueError): + pass + try: + sys.stderr.flush() + except (AttributeError, ValueError): + pass return exitcode diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index 05166b91ba832ae..263fa1850fe0b6f 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -583,26 +583,52 @@ def test_wait_for_threads(self): proc.join() self.assertTrue(evt.is_set()) - @classmethod - def _test_error_on_stdio_flush(self, evt): + @staticmethod + def closeIO(stream_name, evt, type_): + if type_ == 'threads': + # it is safe to close std streams in another process, but if thread + # are used, we must not close the original std streams + closed_stream = _file_like(io.StringIO()) + closed_stream.close() + setattr(sys, stream_name, closed_stream) + else: + # using StringIO is not the same as using a real closed file, + # because a closed StringIO throws no exception when its flush + # method is called + getattr(sys, stream_name).close() evt.set() - def test_error_on_stdio_flush(self): - streams = [io.StringIO(), None] - streams[0].close() + @staticmethod + def removeIO(stream_name, evt, type_): + setattr(sys, stream_name, None) + evt.set() + + def test_closed_stdio(self): + """ + bpo-28326: multiprocessing.Process depends on sys.stdout being open + """ + self.run_process(self.closeIO) + + def test_no_stdio(self): + """ + bpo-31804: set sys.stdio and sys.stderr to None, instead of + changing the Python interpreter to pythonw.exe. (OS independence) + """ + self.run_process(self.removeIO) + + def run_process(self, target): for stream_name in ('stdout', 'stderr'): - for stream in streams: - old_stream = getattr(sys, stream_name) - setattr(sys, stream_name, stream) - try: - evt = self.Event() - proc = self.Process(target=self._test_error_on_stdio_flush, - args=(evt,)) - proc.start() - proc.join() - self.assertTrue(evt.is_set()) - finally: + old_stream = getattr(sys, stream_name) + evt = self.Event() + proc = self.Process(target=target, args=(stream_name, evt, self.TYPE)) + try: + proc.start() + proc.join() + finally: + if self.TYPE == 'threads': setattr(sys, stream_name, old_stream) + self.assertTrue(evt.is_set()) + self.assertEqual(proc.exitcode, 0) @classmethod def _sleep_and_set_event(self, evt, delay=0.0): @@ -653,10 +679,6 @@ def test_forkserver_sigkill(self): self.check_forkserver_death(signal.SIGKILL) -# -# -# - class _UpperCaser(multiprocessing.Process): def __init__(self): @@ -3852,6 +3874,9 @@ def flush(self): self._delegate.write(''.join(self.cache)) self._cache = [] + def close(self): + self._delegate.close() + class TestStdinBadfiledescriptor(unittest.TestCase): def test_queue_in_process(self): diff --git a/Misc/NEWS.d/next/Library/2018-02-12-11-04-46.bpo-31804.9mWA5i.rst b/Misc/NEWS.d/next/Library/2018-02-12-11-04-46.bpo-31804.9mWA5i.rst new file mode 100644 index 000000000000000..e72975c24ed6d50 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-02-12-11-04-46.bpo-31804.9mWA5i.rst @@ -0,0 +1,3 @@ +bugfix: Process return value is 0 when multiprocessing is used with closed +stdout or stderr streams, or if stdout or stderr is None (e.g. using +pythonw.exe on windows)