cpython: f889458c65cc (original) (raw)
Mercurial > cpython
changeset 74979:f889458c65cc 2.7
Issue #2489: Fix bug in _copy loop that could consume 100% cpu on EOF. [#2489]
Gregory P. Smith greg@krypto.org | |
---|---|
date | Thu, 16 Feb 2012 00:40:03 -0800 |
parents | 3b127a415643 |
children | d2b08b5896d9 |
files | Lib/pty.py Lib/test/test_pty.py |
diffstat | 2 files changed, 101 insertions(+), 6 deletions(-)[+] [-] Lib/pty.py 16 Lib/test/test_pty.py 91 |
line wrap: on
line diff
--- a/Lib/pty.py +++ b/Lib/pty.py @@ -142,15 +142,21 @@ def _copy(master_fd, master_read=_read, Copies pty master -> standard output (master_read) standard input -> pty master (stdin_read)"""
- fds = [master_fd, STDIN_FILENO]
- while True:
rfds, wfds, xfds = select(fds, [], [])[](#l1.12) if master_fd in rfds:[](#l1.13) data = master_read(master_fd)[](#l1.14)
os.write(STDOUT_FILENO, data)[](#l1.15)
if not data: # Reached EOF.[](#l1.16)
fds.remove(master_fd)[](#l1.17)
else:[](#l1.18)
os.write(STDOUT_FILENO, data)[](#l1.19) if STDIN_FILENO in rfds:[](#l1.20) data = stdin_read(STDIN_FILENO)[](#l1.21)
_writen(master_fd, data)[](#l1.22)
if not data:[](#l1.23)
fds.remove(STDIN_FILENO)[](#l1.24)
else:[](#l1.25)
_writen(master_fd, data)[](#l1.26)
def spawn(argv, master_read=_read, stdin_read=_read): """Create a spawned process."""
--- a/Lib/test/test_pty.py +++ b/Lib/test/test_pty.py @@ -8,7 +8,9 @@ import errno import pty import os import sys +import select import signal +import socket import unittest TEST_STRING_1 = "I wish to buy a fish license.\n" @@ -193,8 +195,95 @@ class PtyTest(unittest.TestCase): # pty.fork() passed. + +class SmallPtyTests(unittest.TestCase):
- def setUp(self):
self.orig_stdin_fileno = pty.STDIN_FILENO[](#l2.22)
self.orig_stdout_fileno = pty.STDOUT_FILENO[](#l2.23)
self.orig_pty_select = pty.select[](#l2.24)
self.fds = [] # A list of file descriptors to close.[](#l2.25)
self.select_rfds_lengths = [][](#l2.26)
self.select_rfds_results = [][](#l2.27)
- def tearDown(self):
pty.STDIN_FILENO = self.orig_stdin_fileno[](#l2.30)
pty.STDOUT_FILENO = self.orig_stdout_fileno[](#l2.31)
pty.select = self.orig_pty_select[](#l2.32)
for fd in self.fds:[](#l2.33)
try:[](#l2.34)
os.close(fd)[](#l2.35)
except:[](#l2.36)
pass[](#l2.37)
- def _pipe(self):
pipe_fds = os.pipe()[](#l2.40)
self.fds.extend(pipe_fds)[](#l2.41)
return pipe_fds[](#l2.42)
- def _mock_select(self, rfds, wfds, xfds):
# This will raise IndexError when no more expected calls exist.[](#l2.45)
self.assertEqual(self.select_rfds_lengths.pop(0), len(rfds))[](#l2.46)
return self.select_rfds_results.pop(0), [], [][](#l2.47)
- def test__copy_to_each(self):
"""Test the normal data case on both master_fd and stdin."""[](#l2.50)
read_from_stdout_fd, mock_stdout_fd = self._pipe()[](#l2.51)
pty.STDOUT_FILENO = mock_stdout_fd[](#l2.52)
mock_stdin_fd, write_to_stdin_fd = self._pipe()[](#l2.53)
pty.STDIN_FILENO = mock_stdin_fd[](#l2.54)
socketpair = socket.socketpair()[](#l2.55)
masters = [s.fileno() for s in socketpair][](#l2.56)
self.fds.extend(masters)[](#l2.57)
# Feed data. Smaller than PIPEBUF. These writes will not block.[](#l2.59)
os.write(masters[1], b'from master')[](#l2.60)
os.write(write_to_stdin_fd, b'from stdin')[](#l2.61)
# Expect two select calls, the last one will cause IndexError[](#l2.63)
pty.select = self._mock_select[](#l2.64)
self.select_rfds_lengths.append(2)[](#l2.65)
self.select_rfds_results.append([mock_stdin_fd, masters[0]])[](#l2.66)
self.select_rfds_lengths.append(2)[](#l2.67)
with self.assertRaises(IndexError):[](#l2.69)
pty._copy(masters[0])[](#l2.70)
# Test that the right data went to the right places.[](#l2.72)
rfds = select.select([read_from_stdout_fd, masters[1]], [], [], 0)[0][](#l2.73)
self.assertEqual([read_from_stdout_fd, masters[1]], rfds)[](#l2.74)
self.assertEqual(os.read(read_from_stdout_fd, 20), b'from master')[](#l2.75)
self.assertEqual(os.read(masters[1], 20), b'from stdin')[](#l2.76)
- def test__copy_eof_on_all(self):
"""Test the empty read EOF case on both master_fd and stdin."""[](#l2.79)
read_from_stdout_fd, mock_stdout_fd = self._pipe()[](#l2.80)
pty.STDOUT_FILENO = mock_stdout_fd[](#l2.81)
mock_stdin_fd, write_to_stdin_fd = self._pipe()[](#l2.82)
pty.STDIN_FILENO = mock_stdin_fd[](#l2.83)
socketpair = socket.socketpair()[](#l2.84)
masters = [s.fileno() for s in socketpair][](#l2.85)
self.fds.extend(masters)[](#l2.86)
os.close(masters[1])[](#l2.88)
socketpair[1].close()[](#l2.89)
os.close(write_to_stdin_fd)[](#l2.90)
# Expect two select calls, the last one will cause IndexError[](#l2.92)
pty.select = self._mock_select[](#l2.93)
self.select_rfds_lengths.append(2)[](#l2.94)
self.select_rfds_results.append([mock_stdin_fd, masters[0]])[](#l2.95)
# We expect that both fds were removed from the fds list as they[](#l2.96)
# both encountered an EOF before the second select call.[](#l2.97)
self.select_rfds_lengths.append(0)[](#l2.98)
with self.assertRaises(IndexError):[](#l2.100)
pty._copy(masters[0])[](#l2.101)