cpython: c53e3aacb816 (original) (raw)
Mercurial > cpython
changeset 78127:c53e3aacb816 2.7
Fixes Issue #14635: telnetlib will use poll() rather than select() when possible to avoid failing due to the select() file descriptor limit. Contributed by Akintayo Holder and under the Google contributor agreement. [#14635]
Gregory P. Smith greg@krypto.org | |
---|---|
date | Sun, 15 Jul 2012 22:16:06 -0700 |
parents | 872afada51b0 |
children | 076ae30e5dd0 |
files | Lib/telnetlib.py Lib/test/test_telnetlib.py Misc/ACKS Misc/NEWS |
diffstat | 4 files changed, 224 insertions(+), 1 deletions(-)[+] [-] Lib/telnetlib.py 130 Lib/test/test_telnetlib.py 91 Misc/ACKS 1 Misc/NEWS 3 |
line wrap: on
line diff
--- a/Lib/telnetlib.py +++ b/Lib/telnetlib.py @@ -34,6 +34,7 @@ To do:
Imported modules
+import errno import sys import socket import select @@ -205,6 +206,7 @@ class Telnet: self.sb = 0 # flag for SB and SE sequence. self.sbdataq = '' self.option_callback = None
self._has_poll = hasattr(select, 'poll')[](#l1.15) if host is not None:[](#l1.16) self.open(host, port, timeout)[](#l1.17)
@@ -287,6 +289,61 @@ class Telnet: is closed and no cooked data is available. """
if self._has_poll:[](#l1.23)
return self._read_until_with_poll(match, timeout)[](#l1.24)
else:[](#l1.25)
return self._read_until_with_select(match, timeout)[](#l1.26)
- def _read_until_with_poll(self, match, timeout):
"""Read until a given string is encountered or until timeout.[](#l1.29)
This method uses select.poll() to implement the timeout.[](#l1.31)
"""[](#l1.32)
n = len(match)[](#l1.33)
call_timeout = timeout[](#l1.34)
if timeout is not None:[](#l1.35)
from time import time[](#l1.36)
time_start = time()[](#l1.37)
self.process_rawq()[](#l1.38)
i = self.cookedq.find(match)[](#l1.39)
if i < 0:[](#l1.40)
poller = select.poll()[](#l1.41)
poll_in_or_priority_flags = select.POLLIN | select.POLLPRI[](#l1.42)
poller.register(self, poll_in_or_priority_flags)[](#l1.43)
while i < 0 and not self.eof:[](#l1.44)
try:[](#l1.45)
ready = poller.poll(call_timeout)[](#l1.46)
except select.error as e:[](#l1.47)
if e.errno == errno.EINTR:[](#l1.48)
if timeout is not None:[](#l1.49)
elapsed = time() - time_start[](#l1.50)
call_timeout = timeout-elapsed[](#l1.51)
continue[](#l1.52)
raise[](#l1.53)
for fd, mode in ready:[](#l1.54)
if mode & poll_in_or_priority_flags:[](#l1.55)
i = max(0, len(self.cookedq)-n)[](#l1.56)
self.fill_rawq()[](#l1.57)
self.process_rawq()[](#l1.58)
i = self.cookedq.find(match, i)[](#l1.59)
if timeout is not None:[](#l1.60)
elapsed = time() - time_start[](#l1.61)
if elapsed >= timeout:[](#l1.62)
break[](#l1.63)
call_timeout = timeout-elapsed[](#l1.64)
poller.unregister(self)[](#l1.65)
if i >= 0:[](#l1.66)
i = i + n[](#l1.67)
buf = self.cookedq[:i][](#l1.68)
self.cookedq = self.cookedq[i:][](#l1.69)
return buf[](#l1.70)
return self.read_very_lazy()[](#l1.71)
- def _read_until_with_select(self, match, timeout=None):
"""Read until a given string is encountered or until timeout.[](#l1.74)
The timeout is implemented using select.select().[](#l1.76)
"""[](#l1.77) n = len(match)[](#l1.78) self.process_rawq()[](#l1.79) i = self.cookedq.find(match)[](#l1.80)
@@ -589,6 +646,79 @@ class Telnet: results are undeterministic, and may depend on the I/O timing. """
if self._has_poll:[](#l1.85)
return self._expect_with_poll(list, timeout)[](#l1.86)
else:[](#l1.87)
return self._expect_with_select(list, timeout)[](#l1.88)
- def _expect_with_poll(self, expect_list, timeout=None):
"""Read until one from a list of a regular expressions matches.[](#l1.91)
This method uses select.poll() to implement the timeout.[](#l1.93)
"""[](#l1.94)
re = None[](#l1.95)
expect_list = expect_list[:][](#l1.96)
indices = range(len(expect_list))[](#l1.97)
for i in indices:[](#l1.98)
if not hasattr(expect_list[i], "search"):[](#l1.99)
if not re: import re[](#l1.100)
expect_list[i] = re.compile(expect_list[i])[](#l1.101)
call_timeout = timeout[](#l1.102)
if timeout is not None:[](#l1.103)
from time import time[](#l1.104)
time_start = time()[](#l1.105)
self.process_rawq()[](#l1.106)
m = None[](#l1.107)
for i in indices:[](#l1.108)
m = expect_list[i].search(self.cookedq)[](#l1.109)
if m:[](#l1.110)
e = m.end()[](#l1.111)
text = self.cookedq[:e][](#l1.112)
self.cookedq = self.cookedq[e:][](#l1.113)
break[](#l1.114)
if not m:[](#l1.115)
poller = select.poll()[](#l1.116)
poll_in_or_priority_flags = select.POLLIN | select.POLLPRI[](#l1.117)
poller.register(self, poll_in_or_priority_flags)[](#l1.118)
while not m and not self.eof:[](#l1.119)
try:[](#l1.120)
ready = poller.poll(call_timeout)[](#l1.121)
except select.error as e:[](#l1.122)
if e.errno == errno.EINTR:[](#l1.123)
if timeout is not None:[](#l1.124)
elapsed = time() - time_start[](#l1.125)
call_timeout = timeout-elapsed[](#l1.126)
continue[](#l1.127)
raise[](#l1.128)
for fd, mode in ready:[](#l1.129)
if mode & poll_in_or_priority_flags:[](#l1.130)
self.fill_rawq()[](#l1.131)
self.process_rawq()[](#l1.132)
for i in indices:[](#l1.133)
m = expect_list[i].search(self.cookedq)[](#l1.134)
if m:[](#l1.135)
e = m.end()[](#l1.136)
text = self.cookedq[:e][](#l1.137)
self.cookedq = self.cookedq[e:][](#l1.138)
break[](#l1.139)
if timeout is not None:[](#l1.140)
elapsed = time() - time_start[](#l1.141)
if elapsed >= timeout:[](#l1.142)
break[](#l1.143)
call_timeout = timeout-elapsed[](#l1.144)
poller.unregister(self)[](#l1.145)
if m:[](#l1.146)
return (i, m, text)[](#l1.147)
text = self.read_very_lazy()[](#l1.148)
if not text and self.eof:[](#l1.149)
raise EOFError[](#l1.150)
return (-1, None, text)[](#l1.151)
- def _expect_with_select(self, list, timeout=None):
"""Read until one from a list of a regular expressions matches.[](#l1.154)
The timeout is implemented using select.select().[](#l1.156)
"""[](#l1.157) re = None[](#l1.158) list = list[:][](#l1.159) indices = range(len(list))[](#l1.160)
--- a/Lib/test/test_telnetlib.py +++ b/Lib/test/test_telnetlib.py @@ -135,6 +135,28 @@ class ReadTests(TestCase): self.assertEqual(data, want[0]) self.assertEqual(telnet.read_all(), 'not seen')
- def test_read_until_with_poll(self):
"""Use select.poll() to implement telnet.read_until()."""[](#l2.8)
want = ['x' * 10, 'match', 'y' * 10, EOF_sigil][](#l2.9)
self.dataq.put(want)[](#l2.10)
telnet = telnetlib.Telnet(HOST, self.port)[](#l2.11)
if not telnet._has_poll:[](#l2.12)
raise unittest.SkipTest('select.poll() is required')[](#l2.13)
telnet._has_poll = True[](#l2.14)
self.dataq.join()[](#l2.15)
data = telnet.read_until('match')[](#l2.16)
self.assertEqual(data, ''.join(want[:-2]))[](#l2.17)
- def test_read_until_with_select(self):
"""Use select.select() to implement telnet.read_until()."""[](#l2.20)
want = ['x' * 10, 'match', 'y' * 10, EOF_sigil][](#l2.21)
self.dataq.put(want)[](#l2.22)
telnet = telnetlib.Telnet(HOST, self.port)[](#l2.23)
telnet._has_poll = False[](#l2.24)
self.dataq.join()[](#l2.25)
data = telnet.read_until('match')[](#l2.26)
self.assertEqual(data, ''.join(want[:-2]))[](#l2.27)
+ def test_read_all_A(self): """ read_all() @@ -357,8 +379,75 @@ class OptionTests(TestCase): self.assertEqual('', telnet.read_sb_data()) nego.sb_getter = None # break the nego => telnet cycle + +class ExpectTests(TestCase):
- def setUp(self):
self.evt = threading.Event()[](#l2.39)
self.dataq = Queue.Queue()[](#l2.40)
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)[](#l2.41)
self.sock.settimeout(10)[](#l2.42)
self.port = test_support.bind_port(self.sock)[](#l2.43)
self.thread = threading.Thread(target=server, args=(self.evt,self.sock,[](#l2.44)
self.dataq))[](#l2.45)
self.thread.start()[](#l2.46)
self.evt.wait()[](#l2.47)
use a similar approach to testing timeouts as test_timeout.py
these will never pass 100% but make the fuzz big enough that it is rare
- block_long = 0.6
- block_short = 0.3
- def test_expect_A(self):
"""[](#l2.57)
expect(expected, [timeout])[](#l2.58)
Read until the expected string has been seen, or a timeout is[](#l2.59)
hit (default is no timeout); may block.[](#l2.60)
"""[](#l2.61)
want = ['x' * 10, 'match', 'y' * 10, EOF_sigil][](#l2.62)
self.dataq.put(want)[](#l2.63)
telnet = telnetlib.Telnet(HOST, self.port)[](#l2.64)
self.dataq.join()[](#l2.65)
(_,_,data) = telnet.expect(['match'])[](#l2.66)
self.assertEqual(data, ''.join(want[:-2]))[](#l2.67)
- def test_expect_B(self):
# test the timeout - it does NOT raise socket.timeout[](#l2.70)
want = ['hello', self.block_long, 'not seen', EOF_sigil][](#l2.71)
self.dataq.put(want)[](#l2.72)
telnet = telnetlib.Telnet(HOST, self.port)[](#l2.73)
self.dataq.join()[](#l2.74)
(_,_,data) = telnet.expect(['not seen'], self.block_short)[](#l2.75)
self.assertEqual(data, want[0])[](#l2.76)
self.assertEqual(telnet.read_all(), 'not seen')[](#l2.77)
- def test_expect_with_poll(self):
"""Use select.poll() to implement telnet.expect()."""[](#l2.80)
want = ['x' * 10, 'match', 'y' * 10, EOF_sigil][](#l2.81)
self.dataq.put(want)[](#l2.82)
telnet = telnetlib.Telnet(HOST, self.port)[](#l2.83)
if not telnet._has_poll:[](#l2.84)
raise unittest.SkipTest('select.poll() is required')[](#l2.85)
telnet._has_poll = True[](#l2.86)
self.dataq.join()[](#l2.87)
(_,_,data) = telnet.expect(['match'])[](#l2.88)
self.assertEqual(data, ''.join(want[:-2]))[](#l2.89)
- def test_expect_with_select(self):
"""Use select.select() to implement telnet.expect()."""[](#l2.92)
want = ['x' * 10, 'match', 'y' * 10, EOF_sigil][](#l2.93)
self.dataq.put(want)[](#l2.94)
telnet = telnetlib.Telnet(HOST, self.port)[](#l2.95)
telnet._has_poll = False[](#l2.96)
self.dataq.join()[](#l2.97)
(_,_,data) = telnet.expect(['match'])[](#l2.98)
self.assertEqual(data, ''.join(want[:-2]))[](#l2.99)
+ + def test_main(verbose=None):
if name == 'main': test_main()
--- a/Misc/ACKS +++ b/Misc/ACKS @@ -369,6 +369,7 @@ Chris Hoffman Albert Hofkamp Tomas Hoger Jonathan Hogg +Akintayo Holder Gerrit Holl Shane Holloway Rune Holm
--- a/Misc/NEWS +++ b/Misc/NEWS @@ -84,6 +84,9 @@ Core and Builtins Library ------- +- Issue #14635: telnetlib will use poll() rather than select() when possible