]> granicus.if.org Git - python/commitdiff
Issue #2489: Fix bug in _copy loop that could consume 100% cpu on EOF.
authorGregory P. Smith <greg@krypto.org>
Thu, 16 Feb 2012 08:35:43 +0000 (00:35 -0800)
committerGregory P. Smith <greg@krypto.org>
Thu, 16 Feb 2012 08:35:43 +0000 (00:35 -0800)
1  2 
Lib/test/test_pty.py
Misc/NEWS

index c6fc5e7da75de7435da16bf0b52d30c8f468ea14,55ab914f89f8dad109f019ee9aee01ff70fb8631..4f1251cd3683758efa5f887abe3238c3c480553c
@@@ -194,9 -196,96 +196,96 @@@ class PtyTest(unittest.TestCase)
  
          # pty.fork() passed.
  
 -        self.assertSameElements([read_from_stdout_fd, masters[1]], rfds)
+ class SmallPtyTests(unittest.TestCase):
+     """These tests don't spawn children or hang."""
+     def setUp(self):
+         self.orig_stdin_fileno = pty.STDIN_FILENO
+         self.orig_stdout_fileno = pty.STDOUT_FILENO
+         self.orig_pty_select = pty.select
+         self.fds = []  # A list of file descriptors to close.
+         self.select_rfds_lengths = []
+         self.select_rfds_results = []
+     def tearDown(self):
+         pty.STDIN_FILENO = self.orig_stdin_fileno
+         pty.STDOUT_FILENO = self.orig_stdout_fileno
+         pty.select = self.orig_pty_select
+         for fd in self.fds:
+             try:
+                 os.close(fd)
+             except:
+                 pass
+     def _pipe(self):
+         pipe_fds = os.pipe()
+         self.fds.extend(pipe_fds)
+         return pipe_fds
+     def _mock_select(self, rfds, wfds, xfds):
+         # This will raise IndexError when no more expected calls exist.
+         self.assertEqual(self.select_rfds_lengths.pop(0), len(rfds))
+         return self.select_rfds_results.pop(0), [], []
+     def test__copy_to_each(self):
+         """Test the normal data case on both master_fd and stdin."""
+         read_from_stdout_fd, mock_stdout_fd = self._pipe()
+         pty.STDOUT_FILENO = mock_stdout_fd
+         mock_stdin_fd, write_to_stdin_fd = self._pipe()
+         pty.STDIN_FILENO = mock_stdin_fd
+         socketpair = socket.socketpair()
+         masters = [s.fileno() for s in socketpair]
+         self.fds.extend(masters)
+         # Feed data.  Smaller than PIPEBUF.  These writes will not block.
+         os.write(masters[1], b'from master')
+         os.write(write_to_stdin_fd, b'from stdin')
+         # Expect two select calls, the last one will cause IndexError
+         pty.select = self._mock_select
+         self.select_rfds_lengths.append(2)
+         self.select_rfds_results.append([mock_stdin_fd, masters[0]])
+         self.select_rfds_lengths.append(2)
+         with self.assertRaises(IndexError):
+             pty._copy(masters[0])
+         # Test that the right data went to the right places.
+         rfds = select.select([read_from_stdout_fd, masters[1]], [], [], 0)[0]
++        self.assertEqual([read_from_stdout_fd, masters[1]], rfds)
+         self.assertEqual(os.read(read_from_stdout_fd, 20), b'from master')
+         self.assertEqual(os.read(masters[1], 20), b'from stdin')
+     def test__copy_eof_on_all(self):
+         """Test the empty read EOF case on both master_fd and stdin."""
+         read_from_stdout_fd, mock_stdout_fd = self._pipe()
+         pty.STDOUT_FILENO = mock_stdout_fd
+         mock_stdin_fd, write_to_stdin_fd = self._pipe()
+         pty.STDIN_FILENO = mock_stdin_fd
+         socketpair = socket.socketpair()
+         masters = [s.fileno() for s in socketpair]
+         self.fds.extend(masters)
+         os.close(masters[1])
+         socketpair[1].close()
+         os.close(write_to_stdin_fd)
+         # Expect two select calls, the last one will cause IndexError
+         pty.select = self._mock_select
+         self.select_rfds_lengths.append(2)
+         self.select_rfds_results.append([mock_stdin_fd, masters[0]])
+         # We expect that both fds were removed from the fds list as they
+         # both encountered an EOF before the second select call.
+         self.select_rfds_lengths.append(0)
+         with self.assertRaises(IndexError):
+             pty._copy(masters[0])
  def test_main(verbose=None):
      try:
-         run_unittest(PtyTest)
+         run_unittest(SmallPtyTests, PtyTest)
      finally:
          reap_children()
  
diff --cc Misc/NEWS
index 49d18516babe2b7bd88572c9dfe1eb9a390ec21c,8b0e12a8f3baf48aede25a01f94fd17945e1c1cb..a1810aee71e296efc8d3d64090f3869f3c6411f5
+++ b/Misc/NEWS
@@@ -466,11 -116,10 +466,13 @@@ Core and Builtin
  Library
  -------
  
+ - Issue #2489: pty.spawn could consume 100% cpu when it encountered an EOF.
  - Issue #13014: Fix a possible reference leak in SSLSocket.getpeercert().
  
 +- Issue #13777: Add PF_SYSTEM sockets on OS X.
 +  Patch by Michael Goderbauer.
 +
  - Issue #13015: Fix a possible reference leak in defaultdict.__repr__.
    Patch by Suman Saha.