Skip to content

Commit c780ce0

Browse files
committed
pythonGH-134453: Fix subprocess memoryview input handling on POSIX
Fix inconsistent subprocess.Popen.communicate() behavior between Windows and POSIX when using memoryview objects with non-byte elements as input. On POSIX systems, the code was incorrectly comparing bytes written against element count instead of byte count, causing data truncation for large inputs with non-byte element types. Changes: - Cast memoryview inputs to byte view when input is already a memoryview - Fix progress tracking to use len(input_view) instead of len(self._input) - Add comprehensive test coverage for memoryview inputs 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> pre-commit-whitespace-fixup
1 parent 98a5b83 commit c780ce0

File tree

2 files changed

+46
-2
lines changed

2 files changed

+46
-2
lines changed

Lib/subprocess.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2103,7 +2103,10 @@ def _communicate(self, input, endtime, orig_timeout):
21032103
self._save_input(input)
21042104

21052105
if self._input:
2106-
input_view = memoryview(self._input)
2106+
if not isinstance(self._input, memoryview):
2107+
input_view = memoryview(self._input)
2108+
else:
2109+
input_view = self._input.cast("b") # byte input required
21072110

21082111
with _PopenSelector() as selector:
21092112
if self.stdin and input:
@@ -2139,7 +2142,7 @@ def _communicate(self, input, endtime, orig_timeout):
21392142
selector.unregister(key.fileobj)
21402143
key.fileobj.close()
21412144
else:
2142-
if self._input_offset >= len(self._input):
2145+
if self._input_offset >= len(input_view):
21432146
selector.unregister(key.fileobj)
21442147
key.fileobj.close()
21452148
elif key.fileobj in (self.stdout, self.stderr):

Lib/test/test_subprocess.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -957,6 +957,47 @@ def test_communicate(self):
957957
self.assertEqual(stdout, b"banana")
958958
self.assertEqual(stderr, b"pineapple")
959959

960+
def test_communicate_memoryview_input(self):
961+
# Test memoryview input with byte elements
962+
test_data = b"Hello, memoryview!"
963+
mv = memoryview(test_data)
964+
p = subprocess.Popen([sys.executable, "-c",
965+
'import sys; sys.stdout.write(sys.stdin.read())'],
966+
stdin=subprocess.PIPE,
967+
stdout=subprocess.PIPE)
968+
self.addCleanup(p.stdout.close)
969+
self.addCleanup(p.stdin.close)
970+
(stdout, stderr) = p.communicate(mv)
971+
self.assertEqual(stdout, test_data)
972+
self.assertEqual(stderr, None)
973+
974+
def test_communicate_memoryview_input_nonbyte(self):
975+
# Test memoryview input with non-byte elements (e.g., int32)
976+
# This tests the fix for gh-134453 where non-byte memoryviews
977+
# had incorrect length tracking on POSIX
978+
import array
979+
# Create an array of 32-bit integers that's large enough to trigger
980+
# the chunked writing behavior (> PIPE_BUF)
981+
pipe_buf = getattr(select, 'PIPE_BUF', 512)
982+
# Each 'i' element is 4 bytes, so we need more than pipe_buf/4 elements
983+
# Add some extra to ensure we exceed the buffer size
984+
num_elements = (pipe_buf // 4) + 100
985+
test_array = array.array('i', range(num_elements)) # 'i' = signed int (4 bytes each)
986+
expected_bytes = test_array.tobytes()
987+
mv = memoryview(test_array)
988+
989+
p = subprocess.Popen([sys.executable, "-c",
990+
'import sys; '
991+
'data = sys.stdin.buffer.read(); '
992+
'sys.stdout.buffer.write(data)'],
993+
stdin=subprocess.PIPE,
994+
stdout=subprocess.PIPE)
995+
self.addCleanup(p.stdout.close)
996+
self.addCleanup(p.stdin.close)
997+
(stdout, stderr) = p.communicate(mv)
998+
self.assertEqual(stdout, expected_bytes)
999+
self.assertEqual(stderr, None)
1000+
9601001
def test_communicate_timeout(self):
9611002
p = subprocess.Popen([sys.executable, "-c",
9621003
'import sys,os,time;'

0 commit comments

Comments
 (0)