]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Add futures-based Resolver interface, and test with both sync and thread pool.
authorBen Darnell <ben@bendarnell.com>
Fri, 31 Aug 2012 03:10:45 +0000 (23:10 -0400)
committerBen Darnell <ben@bendarnell.com>
Fri, 31 Aug 2012 03:10:45 +0000 (23:10 -0400)
tornado/concurrent.py [new file with mode: 0644]
tornado/ioloop.py
tornado/netutil.py
tornado/test/ioloop_test.py
tornado/test/netutil_test.py [new file with mode: 0644]
tornado/test/runtests.py

diff --git a/tornado/concurrent.py b/tornado/concurrent.py
new file mode 100644 (file)
index 0000000..9bd0bac
--- /dev/null
@@ -0,0 +1,78 @@
+#!/usr/bin/env python
+#
+# Copyright 2012 Facebook
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+from __future__ import absolute_import, division, with_statement
+
+import functools
+import sys
+
+from tornado.util import raise_exc_info
+
+try:
+    from concurrent import futures
+except ImportError:
+    futures = None
+
+
+class DummyFuture(object):
+    def __init__(self, result, exc_info=None):
+        self._result = result
+        self._exc_info = exc_info
+
+    def cancel(self):
+        return False
+
+    def cancelled(self):
+        return False
+
+    def running(self):
+        return False
+
+    def done(self):
+        return True
+
+    def result(self, timeout=None):
+        if self._exc_info:
+            raise_exc_info(self._exc_info)
+        return self._result
+
+    def exception(self, timeout=None):
+        if self._exc_info:
+            return self._exc_info[1]
+        else:
+            return None
+
+    def add_done_callback(self, fn):
+        fn(self)
+
+
+class DummyExecutor(object):
+    def submit(self, fn, *args, **kwargs):
+        try:
+            return DummyFuture(fn(*args, **kwargs))
+        except Exception:
+            return DummyFuture(result=None, exc_info=sys.exc_info())
+
+dummy_executor = DummyExecutor()
+
+def run_on_executor(fn):
+    @functools.wraps(fn)
+    def wrapper(self, *args, **kwargs):
+        callback = kwargs.pop("callback")
+        future = self.executor.submit(fn, self, *args, **kwargs)
+        if callback:
+            self.io_loop.add_future(future, callback)
+        return future
+    return wrapper
index 7525bb0996dfa103949d5e7037397d30bc292b82..4e9267240d7978f9cdfd65020d26d7616d30c92d 100644 (file)
@@ -40,6 +40,7 @@ import threading
 import time
 import traceback
 
+from tornado.concurrent import DummyFuture
 from tornado import stack_context
 
 try:
@@ -422,13 +423,14 @@ class IOLoop(object):
             # avoid it when we can.
             self._waker.wake()
 
+    if futures is not None:
+        _FUTURE_TYPES = (futures.Future, DummyFuture)
+    else:
+        _FUTURE_TYPES = DummyFuture
     def add_future(self, future, callback):
         """Schedules a callback on the IOLoop when the given future is finished.
-
-        Requires the concurrent.futures module (standard in python 3.2+,
-        available via "pip install futures" in older versions).
         """
-        assert isinstance(future, futures.Future)
+        assert isinstance(future, IOLoop._FUTURE_TYPES)
         future.add_done_callback(
             lambda future: self.add_callback(
                 functools.partial(callback, future)))
index ba0b27d25b19877441fa18189082c63a8a5b9c81..cb5ef089a482e1d067488082a7f2ce88263c5cd5 100644 (file)
 from __future__ import absolute_import, division, with_statement
 
 import errno
+import functools
 import logging
 import os
 import socket
 import stat
 
 from tornado import process
+from tornado.concurrent import DummyFuture, dummy_executor, run_on_executor
 from tornado.ioloop import IOLoop
 from tornado.iostream import IOStream, SSLIOStream
 from tornado.platform.auto import set_close_exec
@@ -341,3 +343,13 @@ def add_accept_handler(sock, callback, io_loop=None):
                 raise
             callback(connection, address)
     io_loop.add_handler(sock.fileno(), accept_handler, IOLoop.READ)
+
+
+class Resolver(object):
+    def __init__(self, io_loop=None, executor=None):
+        self.io_loop = io_loop or IOLoop.instance()
+        self.executor = executor or dummy_executor
+
+    @run_on_executor
+    def getaddrinfo(self, *args, **kwargs):
+        return socket.getaddrinfo(*args, **kwargs)
index 1413a1846bb933a365c137ededb66f9c98cfb61f..0bac29eeb098279458b4010327fd4675c641b2b3 100644 (file)
@@ -63,7 +63,7 @@ class TestIOLoopFutures(AsyncTestCase, LogTrapTestCase):
             self.assertTrue(future.done())
             self.assertTrue(future.result() is None)
 TestIOLoopFutures = unittest.skipIf(
-    futures is None, "futures module is not present")(TestIOLoopFutures)
+    futures is None, "futures module not present")(TestIOLoopFutures)
 
 
 if __name__ == "__main__":
diff --git a/tornado/test/netutil_test.py b/tornado/test/netutil_test.py
new file mode 100644 (file)
index 0000000..69c0ae3
--- /dev/null
@@ -0,0 +1,37 @@
+from __future__ import absolute_import, division, with_statement
+
+import socket
+
+from tornado.netutil import Resolver
+from tornado.testing import AsyncTestCase
+from tornado.test.util import unittest
+
+try:
+    from concurrent import futures
+except ImportError:
+    futures = None
+
+class _ResolverTestMixin(object):
+    def test_localhost(self):
+        self.resolver.getaddrinfo('localhost', 80, socket.AF_UNSPEC,
+                                  socket.SOCK_STREAM,
+                                  callback=self.stop)
+        future = self.wait()
+        self.assertIn(
+            (socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, '',
+             ('127.0.0.1', 80)),
+            future.result())
+
+
+class SyncResolverTest(AsyncTestCase, _ResolverTestMixin):
+    def setUp(self):
+        super(SyncResolverTest, self).setUp()
+        self.resolver = Resolver(self.io_loop)
+
+class ThreadedResolverTest(AsyncTestCase, _ResolverTestMixin):
+    def setUp(self):
+        super(ThreadedResolverTest, self).setUp()
+        from concurrent.futures import ThreadPoolExecutor
+        self.resolver = Resolver(self.io_loop, ThreadPoolExecutor(2))
+ThreadedResolverTest = unittest.skipIf(
+    futures is None, "futures module not present")(ThreadedResolverTest)
index cba692f21702696862014621f6ecb7bd0472f5ba..90d94400f992c3f8c1e97b266f0e2026e63257d4 100644 (file)
@@ -20,6 +20,7 @@ TEST_MODULES = [
     'tornado.test.ioloop_test',
     'tornado.test.iostream_test',
     'tornado.test.locale_test',
+    'tornado.test.netutil_test',
     'tornado.test.options_test',
     'tornado.test.process_test',
     'tornado.test.simple_httpclient_test',