Index: Lib/test/test_subprocess.py =================================================================== --- Lib/test/test_subprocess.py (revision 88124) +++ Lib/test/test_subprocess.py (working copy) @@ -1200,7 +1200,66 @@ " non-zero with this error:\n%s" % stderr.decode('utf8')) + def test_short_lived_subprocess(self): + # If we start a short-lived process which finishes before we begin + # communicating with it, we can receive a SIGPIPE due to the receiving + # process no longer existing. This becomes an EPIPE, which becomes an: + # OSError: [Errno 32] Broken pipe + # Verify that this case is hidden from the user: + # See also http://bugs.python.org/issue1615376 + + # Verify for a "successful" subprocess: + + p = subprocess.Popen([sys.executable, '-c', + ('import sys;' + ' sys.stdout.write("stdout");' + ' sys.stderr.write("stderr");')], + close_fds=True, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + # Try to ensure that the subprocess is finished before attempting + # communication: + time.sleep(0.5) + + # Now begin trying to send bytes to stdin of the subprocess; typically + # the subprocess will already have exited: + out, err = p.communicate(b'\n') + + # The SIGPIPE ought to be transparent to the user: + self.assertEqual(out, b'stdout') + self.assertStderrEqual(err, b'stderr') + self.assertEqual(p.returncode, 0) + + # Now the same, but for a crashing subprocess + + # Verify that we detect an "unexpected" crash (using a bogus ctypes + # call to induce a probable segmentation fault): + support.import_module('ctypes') + + p = subprocess.Popen([sys.executable, '-c', + 'import ctypes; ctypes.string_at(0xffffffff)'], + close_fds=True, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + + # Try to ensure that the subprocess is finished before attempting + # communication: + time.sleep(0.5) + + # Now begin trying to send bytes to stdin of the subprocess; typically + # the subprocess will already have crashed: + out, err = p.communicate(b'\n') + + # The SIGPIPE ought to be transparent to the user, and the return code + # should indicate the SIGSEGV: + self.assertEqual(out, b'') + self.assertStderrEqual(err, b'') + self.assertEqual(p.returncode, -signal.SIGSEGV) + + @unittest.skipUnless(mswindows, "Windows specific tests") class Win32ProcessTestCase(BaseTestCase): Index: Lib/subprocess.py =================================================================== --- Lib/subprocess.py (revision 88124) +++ Lib/subprocess.py (working copy) @@ -1457,7 +1457,14 @@ for fd, mode in ready: if mode & select.POLLOUT: chunk = input[input_offset : input_offset + _PIPE_BUF] - input_offset += os.write(fd, chunk) + try: + input_offset += os.write(fd, chunk) + except OSError as e: + if e.errno == errno.EPIPE: + # Broken pipe: + close_unregister_and_remove(fd) + else: + raise if input_offset >= len(input): close_unregister_and_remove(fd) elif mode & select_POLLIN_POLLPRI: @@ -1501,8 +1508,15 @@ if self.stdin in wlist: chunk = input[input_offset : input_offset + _PIPE_BUF] - bytes_written = os.write(self.stdin.fileno(), chunk) - input_offset += bytes_written + try: + bytes_written = os.write(self.stdin.fileno(), chunk) + input_offset += bytes_written + except OSError as e: + if e.errno == errno.EPIPE: + # Broken pipe: + write_set.remove(self.stdin) + else: + raise if input_offset >= len(input): self.stdin.close() write_set.remove(self.stdin)