From cc996b57890a251cef83101d5cfcbc58179cba5f Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 17 Jul 2014 12:25:27 +0200 Subject: [PATCH] asyncio, tulip issue 190: Process.communicate() must ignore BrokenPipeError If you want to handle the BrokenPipeError, you can easily reimplement communicate(). Add also a unit test to ensure that stdin.write() + stdin.drain() raises BrokenPipeError. --- Doc/library/asyncio-subprocess.rst | 7 ++++++ Lib/asyncio/subprocess.py | 6 +++++- Lib/test/test_asyncio/test_subprocess.py | 27 ++++++++++++++++++------ 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/Doc/library/asyncio-subprocess.rst b/Doc/library/asyncio-subprocess.rst index cba3aae22a6..f0ffbf0b8ec 100644 --- a/Doc/library/asyncio-subprocess.rst +++ b/Doc/library/asyncio-subprocess.rst @@ -191,6 +191,10 @@ Process process, or ``None``, if no data should be sent to the child. The type of *input* must be bytes. + If a :exc:`BrokenPipeError` is raised when writing *input* into stdin, + the exception is ignored. It occurs when the process exits before all + data are written into stdin. + :meth:`communicate` returns a tuple ``(stdoutdata, stderrdata)``. Note that if you want to send data to the process's stdin, you need to @@ -205,6 +209,9 @@ Process This method is a :ref:`coroutine `. + .. versionchanged:: 3.4.2 + The method now ignores :exc:`BrokenPipeError`. + .. method:: kill() Kills the child. On Posix OSs the function sends :py:data:`SIGKILL` to diff --git a/Lib/asyncio/subprocess.py b/Lib/asyncio/subprocess.py index 12902f1bcd9..23d6b4da92b 100644 --- a/Lib/asyncio/subprocess.py +++ b/Lib/asyncio/subprocess.py @@ -143,7 +143,11 @@ class Process: if self._loop.get_debug(): logger.debug('%r communicate: feed stdin (%s bytes)', self, len(input)) - yield from self.stdin.drain() + try: + yield from self.stdin.drain() + except BrokenPipeError: + # ignore BrokenPipeError + pass if self._loop.get_debug(): logger.debug('%r communicate: close stdin', self) diff --git a/Lib/test/test_asyncio/test_subprocess.py b/Lib/test/test_asyncio/test_subprocess.py index 3204d42e07b..e41cabefe85 100644 --- a/Lib/test/test_asyncio/test_subprocess.py +++ b/Lib/test/test_asyncio/test_subprocess.py @@ -11,9 +11,6 @@ if sys.platform != 'win32': # Program blocking PROGRAM_BLOCKED = [sys.executable, '-c', 'import time; time.sleep(3600)'] -# Program sleeping during 1 second -PROGRAM_SLEEP_1SEC = [sys.executable, '-c', 'import time; time.sleep(1)'] - # Program copying input to output PROGRAM_CAT = [ sys.executable, '-c', @@ -118,16 +115,32 @@ class SubprocessMixin: returncode = self.loop.run_until_complete(proc.wait()) self.assertEqual(-signal.SIGHUP, returncode) - def test_broken_pipe(self): + def prepare_broken_pipe_test(self): + # buffer large enough to feed the whole pipe buffer large_data = b'x' * support.PIPE_MAX_SIZE + # the program ends before the stdin can be feeded create = asyncio.create_subprocess_exec( - *PROGRAM_SLEEP_1SEC, + sys.executable, '-c', 'pass', stdin=subprocess.PIPE, loop=self.loop) proc = self.loop.run_until_complete(create) - with self.assertRaises(BrokenPipeError): - self.loop.run_until_complete(proc.communicate(large_data)) + return (proc, large_data) + + def test_stdin_broken_pipe(self): + proc, large_data = self.prepare_broken_pipe_test() + + # drain() must raise BrokenPipeError + proc.stdin.write(large_data) + self.assertRaises(BrokenPipeError, + self.loop.run_until_complete, proc.stdin.drain()) + self.loop.run_until_complete(proc.wait()) + + def test_communicate_ignore_broken_pipe(self): + proc, large_data = self.prepare_broken_pipe_test() + + # communicate() must ignore BrokenPipeError when feeding stdin + self.loop.run_until_complete(proc.communicate(large_data)) self.loop.run_until_complete(proc.wait())