]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Add a c-ares-based resolver implementation.
authorBen Darnell <ben@bendarnell.com>
Sun, 24 Feb 2013 17:25:04 +0000 (12:25 -0500)
committerBen Darnell <ben@bendarnell.com>
Sun, 24 Feb 2013 17:25:04 +0000 (12:25 -0500)
tornado/platform/caresresolver.py [new file with mode: 0644]
tox.ini

diff --git a/tornado/platform/caresresolver.py b/tornado/platform/caresresolver.py
new file mode 100644 (file)
index 0000000..11dafd4
--- /dev/null
@@ -0,0 +1,72 @@
+import pycares
+import socket
+
+from tornado.concurrent import return_future
+from tornado import gen
+from tornado.ioloop import IOLoop
+from tornado.netutil import Resolver, is_valid_ip
+
+class CaresResolver(Resolver):
+    """Name resolver based on the c-ares library.
+
+    This is a non-blocking and non-threaded resolver.  It may not produce
+    the same results as the system resolver, but can be used for non-blocking
+    resolution when threads cannot be used.
+    """
+    def initialize(self, io_loop=None):
+        self.io_loop = io_loop or IOLoop.instance()
+        self.channel = pycares.Channel(sock_state_cb=self._sock_state_cb)
+        self.fds = {}
+
+    def _sock_state_cb(self, fd, readable, writable):
+        state = ((IOLoop.READ if readable else 0) |
+                 (IOLoop.WRITE if writable else 0))
+        if not state:
+            self.io_loop.remove_handler(fd)
+            del self.fds[fd]
+        elif fd in self.fds:
+            self.io_loop.update_handler(fd, state)
+            self.fds[fd] = state
+        else:
+            self.io_loop.add_handler(fd, self._handle_events, state)
+            self.fds[fd] = state
+
+    def _handle_events(self, fd, events):
+        read_fd = pycares.ARES_SOCKET_BAD
+        write_fd = pycares.ARES_SOCKET_BAD
+        if events & IOLoop.READ:
+            read_fd = fd
+        if events & IOLoop.WRITE:
+            write_fd = fd
+        self.channel.process_fd(read_fd, write_fd)
+
+    @return_future
+    @gen.engine
+    def getaddrinfo(self, host, port, family=0, socktype=0, proto=0,
+                    flags=0, callback=None):
+        if is_valid_ip(host):
+            addresses = [host]
+        else:
+            # gethostbyname doesn't take callback as a kwarg
+            self.channel.gethostbyname(host, family, (yield gen.Callback(1)))
+            callback_args = yield gen.Wait(1)
+            assert isinstance(callback_args, gen.Arguments)
+            assert not callback_args.kwargs
+            result, error = callback_args.args
+            if error:
+                raise Exception('C-Ares returned error %s: %s while resolving %s' %
+                                (error, pycares.errno.strerror(error), host))
+            addresses = result.addresses
+        addrinfo = []
+        for address in addresses:
+            if '.' in address:
+                address_family = socket.AF_INET
+            elif ':' in address:
+                address_family = socket.AF_INET6
+            else:
+                address_family = socket.AF_UNSPEC
+            if family != socket.AF_UNSPEC and family != address_family:
+                raise Exception('Requested socket family %d but got %d' %
+                                (family, address_family))
+            addrinfo.append((address_family, socktype, proto, '', (address, port)))
+        callback(addrinfo)
diff --git a/tox.ini b/tox.ini
index 7fea5bea78354ee72bcad1578557a69f8e306ff1..e84a50e22fadf1fe673b4a1ced95b2834b8269be 100644 (file)
--- a/tox.ini
+++ b/tox.ini
@@ -11,7 +11,7 @@
 [tox]
 # "-full" variants include optional dependencies, to ensure
 # that things work both in a bare install and with all the extras.
-envlist = py27-full, py27-curl, py32-full, pypy, py26, py26-full, py27, py32, py32-utf8, py33, py27-opt, py32-opt, pypy-full, py27-select, py27-monotonic, py33-monotonic, py27-twisted, py27-threadedresolver, py27-twistedresolver, py27-twistedlayered
+envlist = py27-full, py27-curl, py32-full, pypy, py26, py26-full, py27, py32, py32-utf8, py33, py27-opt, py32-opt, pypy-full, py27-select, py27-monotonic, py33-monotonic, py27-twisted, py27-threadedresolver, py27-twistedresolver, py27-twistedlayered, py27-caresresolver, py32-caresresolver
 [testenv]
 commands = python -m tornado.test.runtests {posargs:}
 
@@ -118,6 +118,16 @@ deps =
      twisted
 commands = python -m tornado.test.runtests --ioloop=tornado.test.twisted_test.LayeredTwistedIOLoop --resolver=tornado.platform.twisted.TwistedResolver {posargs:}
 
+[testenv:py27-caresresolver]
+basepython = python2.7
+deps =
+     futures
+     mock
+     pycares
+     pycurl
+     twisted
+commands = python -m tornado.test.runtests --resolver=tornado.platform.caresresolver.CaresResolver {posargs:}
+
 [testenv:pypy-full]
 # This configuration works with pypy 1.9.  pycurl installs ok but
 # curl_httpclient doesn't work.  Twisted works most of the time, but
@@ -157,6 +167,13 @@ basepython = python3.3
 basepython = python3.3
 commands = python -m tornado.test.runtests --ioloop_time_monotonic {posargs:}
 
+[testenv:py32-caresresolver]
+basepython = python3.2
+deps =
+     pycares
+commands = python -m tornado.test.runtests --resolver=tornado.platform.caresresolver.CaresResolver {posargs:}
+
+
 # Python's optimized mode disables the assert statement, so run the
 # tests in this mode to ensure we haven't fallen into the trap of relying
 # on an assertion's side effects or using them for things that should be