mirror of
https://github.com/python/cpython.git
synced 2025-08-28 20:56:54 +00:00
merge from 3.5 - Fixes Issue #26373: subprocess.Popen.communicate
now correctly ignores BrokenPipeError when the child process dies before .communicate() is called in more (all?) circumstances.
This commit is contained in:
commit
fcbf1ca4f9
3 changed files with 69 additions and 5 deletions
|
@ -1036,8 +1036,7 @@ class Popen(object):
|
||||||
try:
|
try:
|
||||||
self.stdin.write(input)
|
self.stdin.write(input)
|
||||||
except BrokenPipeError:
|
except BrokenPipeError:
|
||||||
# communicate() must ignore broken pipe error
|
pass # communicate() must ignore broken pipe errors.
|
||||||
pass
|
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
if e.errno == errno.EINVAL and self.poll() is not None:
|
if e.errno == errno.EINVAL and self.poll() is not None:
|
||||||
# Issue #19612: On Windows, stdin.write() fails with EINVAL
|
# Issue #19612: On Windows, stdin.write() fails with EINVAL
|
||||||
|
@ -1045,7 +1044,15 @@ class Popen(object):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
try:
|
||||||
self.stdin.close()
|
self.stdin.close()
|
||||||
|
except BrokenPipeError:
|
||||||
|
pass # communicate() must ignore broken pipe errors.
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno == errno.EINVAL and self.poll() is not None:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
def communicate(self, input=None, timeout=None):
|
def communicate(self, input=None, timeout=None):
|
||||||
"""Interact with process: Send data to stdin. Read data from
|
"""Interact with process: Send data to stdin. Read data from
|
||||||
|
@ -1691,9 +1698,15 @@ class Popen(object):
|
||||||
if self.stdin and not self._communication_started:
|
if self.stdin and not self._communication_started:
|
||||||
# Flush stdio buffer. This might block, if the user has
|
# Flush stdio buffer. This might block, if the user has
|
||||||
# been writing to .stdin in an uncontrolled fashion.
|
# been writing to .stdin in an uncontrolled fashion.
|
||||||
|
try:
|
||||||
self.stdin.flush()
|
self.stdin.flush()
|
||||||
|
except BrokenPipeError:
|
||||||
|
pass # communicate() must ignore BrokenPipeError.
|
||||||
if not input:
|
if not input:
|
||||||
|
try:
|
||||||
self.stdin.close()
|
self.stdin.close()
|
||||||
|
except BrokenPipeError:
|
||||||
|
pass # communicate() must ignore BrokenPipeError.
|
||||||
|
|
||||||
stdout = None
|
stdout = None
|
||||||
stderr = None
|
stderr = None
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import unittest
|
import unittest
|
||||||
|
from unittest import mock
|
||||||
from test import support
|
from test import support
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
@ -1237,6 +1238,52 @@ class ProcessTestCase(BaseTestCase):
|
||||||
fds_after_exception = os.listdir(fd_directory)
|
fds_after_exception = os.listdir(fd_directory)
|
||||||
self.assertEqual(fds_before_popen, fds_after_exception)
|
self.assertEqual(fds_before_popen, fds_after_exception)
|
||||||
|
|
||||||
|
def test_communicate_BrokenPipeError_stdin_close(self):
|
||||||
|
# By not setting stdout or stderr or a timeout we force the fast path
|
||||||
|
# that just calls _stdin_write() internally due to our mock.
|
||||||
|
proc = subprocess.Popen([sys.executable, '-c', 'pass'])
|
||||||
|
with proc, mock.patch.object(proc, 'stdin') as mock_proc_stdin:
|
||||||
|
mock_proc_stdin.close.side_effect = BrokenPipeError
|
||||||
|
proc.communicate() # Should swallow BrokenPipeError from close.
|
||||||
|
mock_proc_stdin.close.assert_called_with()
|
||||||
|
|
||||||
|
def test_communicate_BrokenPipeError_stdin_write(self):
|
||||||
|
# By not setting stdout or stderr or a timeout we force the fast path
|
||||||
|
# that just calls _stdin_write() internally due to our mock.
|
||||||
|
proc = subprocess.Popen([sys.executable, '-c', 'pass'])
|
||||||
|
with proc, mock.patch.object(proc, 'stdin') as mock_proc_stdin:
|
||||||
|
mock_proc_stdin.write.side_effect = BrokenPipeError
|
||||||
|
proc.communicate(b'stuff') # Should swallow the BrokenPipeError.
|
||||||
|
mock_proc_stdin.write.assert_called_once_with(b'stuff')
|
||||||
|
mock_proc_stdin.close.assert_called_once_with()
|
||||||
|
|
||||||
|
def test_communicate_BrokenPipeError_stdin_flush(self):
|
||||||
|
# Setting stdin and stdout forces the ._communicate() code path.
|
||||||
|
# python -h exits faster than python -c pass (but spams stdout).
|
||||||
|
proc = subprocess.Popen([sys.executable, '-h'],
|
||||||
|
stdin=subprocess.PIPE,
|
||||||
|
stdout=subprocess.PIPE)
|
||||||
|
with proc, mock.patch.object(proc, 'stdin') as mock_proc_stdin, \
|
||||||
|
open('/dev/null', 'wb') as dev_null:
|
||||||
|
mock_proc_stdin.flush.side_effect = BrokenPipeError
|
||||||
|
# because _communicate registers a selector using proc.stdin...
|
||||||
|
mock_proc_stdin.fileno.return_value = dev_null.fileno()
|
||||||
|
# _communicate() should swallow BrokenPipeError from flush.
|
||||||
|
proc.communicate(b'stuff')
|
||||||
|
mock_proc_stdin.flush.assert_called_once_with()
|
||||||
|
|
||||||
|
def test_communicate_BrokenPipeError_stdin_close_with_timeout(self):
|
||||||
|
# Setting stdin and stdout forces the ._communicate() code path.
|
||||||
|
# python -h exits faster than python -c pass (but spams stdout).
|
||||||
|
proc = subprocess.Popen([sys.executable, '-h'],
|
||||||
|
stdin=subprocess.PIPE,
|
||||||
|
stdout=subprocess.PIPE)
|
||||||
|
with proc, mock.patch.object(proc, 'stdin') as mock_proc_stdin:
|
||||||
|
mock_proc_stdin.close.side_effect = BrokenPipeError
|
||||||
|
# _communicate() should swallow BrokenPipeError from close.
|
||||||
|
proc.communicate(timeout=999)
|
||||||
|
mock_proc_stdin.close.assert_called_once_with()
|
||||||
|
|
||||||
|
|
||||||
class RunFuncTestCase(BaseTestCase):
|
class RunFuncTestCase(BaseTestCase):
|
||||||
def run_python(self, code, **kwargs):
|
def run_python(self, code, **kwargs):
|
||||||
|
|
|
@ -27,6 +27,10 @@ Core and Builtins
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Issue #26373: subprocess.Popen.communicate now correctly ignores
|
||||||
|
BrokenPipeError when the child process dies before .communicate()
|
||||||
|
is called in more/all circumstances.
|
||||||
|
|
||||||
- signal, socket, and ssl module IntEnum constant name lookups now return a
|
- signal, socket, and ssl module IntEnum constant name lookups now return a
|
||||||
consistent name for values having multiple names. Ex: signal.Signals(6)
|
consistent name for values having multiple names. Ex: signal.Signals(6)
|
||||||
now refers to itself as signal.SIGALRM rather than flipping between that
|
now refers to itself as signal.SIGALRM rather than flipping between that
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue