From e8f5d9f6b142d85e7e74dc4287db3d28b9139a5f Mon Sep 17 00:00:00 2001 From: Bob Halley Date: Wed, 27 Oct 2010 21:30:43 +0100 Subject: [PATCH] add poll() support; bump version to 1.9.0 --- ChangeLog | 6 ++++ README | 10 +++++- dns/query.py | 82 +++++++++++++++++++++++++++++++++++++++++------ dns/version.py | 4 +-- tests/resolver.py | 24 +++++++++++++- 5 files changed, 113 insertions(+), 13 deletions(-) diff --git a/ChangeLog b/ChangeLog index 5a90235a..d1620982 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +2010-10-27 Bob Halley + + * Incorporate a patch to use poll() instead of select() by + default on platforms which support it. Thanks to + Peter Schüller and Spotify for the contribution. + 2010-10-17 Bob Halley * Python prior to 2.5.2 doesn't compute the correct values for diff --git a/README b/README index b313d1c1..72e3e15f 100644 --- a/README +++ b/README @@ -22,7 +22,15 @@ development by continuing to employ the author :). ABOUT THIS RELEASE -This is dnspython 1.8.0 +This is dnspython 1.9.0 + +New since 1.8.0: + + dnspython now uses poll() instead of select() when available. + +Bugs fixed since 1.8.0 + + XXX TBS XXX New since 1.7.1: diff --git a/dns/query.py b/dns/query.py index 4d8379ee..9dc88a63 100644 --- a/dns/query.py +++ b/dns/query.py @@ -45,7 +45,59 @@ def _compute_expiration(timeout): else: return time.time() + timeout -def _wait_for(ir, iw, ix, expiration): +def _poll_for(fd, readable, writable, error, timeout): + """ + @param fd: File descriptor (int). + @param readable: Whether to wait for readability (bool). + @param writable: Whether to wait for writability (bool). + @param expiration: Deadline timeout (expiration time, in seconds (float)). + + @return True on success, False on timeout + """ + event_mask = 0 + if readable: + event_mask |= select.POLLIN + if writable: + event_mask |= select.POLLOUT + if error: + event_mask |= select.POLLERR + + pollable = select.poll() + pollable.register(fd, event_mask) + + if timeout: + event_list = pollable.poll(long(timeout * 1000)) + else: + event_list = pollable.poll() + + return bool(event_list) + +def _select_for(fd, readable, writable, error, timeout): + """ + @param fd: File descriptor (int). + @param readable: Whether to wait for readability (bool). + @param writable: Whether to wait for writability (bool). + @param expiration: Deadline timeout (expiration time, in seconds (float)). + + @return True on success, False on timeout + """ + rset, wset, xset = [], [], [] + + if readable: + rset = [fd] + if writable: + wset = [fd] + if error: + xset = [fd] + + if timeout is None: + (rcount, wcount, xcount) = select.select(rset, wset, xset) + else: + (rcount, wcount, xcount) = select.select(rset, wset, xset, timeout) + + return bool((rcount or wcount or xcount)) + +def _wait_for(fd, readable, writable, error, expiration): done = False while not done: if expiration is None: @@ -55,22 +107,34 @@ def _wait_for(ir, iw, ix, expiration): if timeout <= 0.0: raise dns.exception.Timeout try: - if timeout is None: - (r, w, x) = select.select(ir, iw, ix) - else: - (r, w, x) = select.select(ir, iw, ix, timeout) + if not _polling_backend(fd, readable, writable, error, timeout): + raise dns.exception.Timeout except select.error, e: if e.args[0] != errno.EINTR: raise e done = True - if len(r) == 0 and len(w) == 0 and len(x) == 0: - raise dns.exception.Timeout + +def _set_polling_backend(fn): + """ + Internal API. Do not use. + """ + global _polling_backend + + _polling_backend = fn + +if hasattr(select, 'poll'): + # Prefer poll() on platforms that support it because it has no + # limits on the maximum value of a file descriptor (plus it will + # be more efficient for high values). + _polling_backend = _poll_for +else: + _polling_backend = _select_for def _wait_for_readable(s, expiration): - _wait_for([s], [], [s], expiration) + _wait_for(s, True, False, True, expiration) def _wait_for_writable(s, expiration): - _wait_for([], [s], [s], expiration) + _wait_for(s, False, True, True, expiration) def _addresses_equal(af, a1, a2): # Convert the first value of the tuple, which is a textual format diff --git a/dns/version.py b/dns/version.py index dd135a13..251079f4 100644 --- a/dns/version.py +++ b/dns/version.py @@ -16,8 +16,8 @@ """dnspython release version information.""" MAJOR = 1 -MINOR = 8 -MICRO = 1 +MINOR = 9 +MICRO = 0 RELEASELEVEL = 0x0f SERIAL = 0 diff --git a/tests/resolver.py b/tests/resolver.py index 4cacbdc7..bd6dc5fb 100644 --- a/tests/resolver.py +++ b/tests/resolver.py @@ -14,6 +14,7 @@ # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. import cStringIO +import select import sys import time import unittest @@ -46,7 +47,7 @@ example. 1 IN A 10.0.0.1 ;ADDITIONAL """ -class ResolverTestCase(unittest.TestCase): +class BaseResolverTests(object): if sys.platform != 'win32': def testRead(self): @@ -101,5 +102,26 @@ class ResolverTestCase(unittest.TestCase): zname = dns.resolver.zone_for_name(name) self.failUnlessRaises(dns.resolver.NotAbsolute, bad) +class PollingMonkeyPatchMixin(object): + def setUp(self): + self.__native_polling_backend = dns.query._polling_backend + dns.query._set_polling_backend(self.polling_backend()) + + unittest.TestCase.setUp(self) + + def tearDown(self): + dns.query._set_polling_backend(self.__native_polling_backend) + + unittest.TestCase.tearDown(self) + +class SelectResolverTestCase(PollingMonkeyPatchMixin, BaseResolverTests, unittest.TestCase): + def polling_backend(self): + return dns.query._select_for + +if hasattr(select, 'poll'): + class PollResolverTestCase(PollingMonkeyPatchMixin, BaseResolverTests, unittest.TestCase): + def polling_backend(self): + return dns.query._poll_for + if __name__ == '__main__': unittest.main() -- 2.47.3