From: Ben Darnell Date: Fri, 31 Aug 2012 03:10:45 +0000 (-0400) Subject: Add futures-based Resolver interface, and test with both sync and thread pool. X-Git-Tag: v3.0.0~263^2~9 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=47bb3824b8b7dff43fb8d5c71790a2f0e565f155;p=thirdparty%2Ftornado.git Add futures-based Resolver interface, and test with both sync and thread pool. --- diff --git a/tornado/concurrent.py b/tornado/concurrent.py new file mode 100644 index 000000000..9bd0bac37 --- /dev/null +++ b/tornado/concurrent.py @@ -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 diff --git a/tornado/ioloop.py b/tornado/ioloop.py index 7525bb099..4e9267240 100644 --- a/tornado/ioloop.py +++ b/tornado/ioloop.py @@ -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))) diff --git a/tornado/netutil.py b/tornado/netutil.py index ba0b27d25..cb5ef089a 100644 --- a/tornado/netutil.py +++ b/tornado/netutil.py @@ -19,12 +19,14 @@ 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) diff --git a/tornado/test/ioloop_test.py b/tornado/test/ioloop_test.py index 1413a1846..0bac29eeb 100644 --- a/tornado/test/ioloop_test.py +++ b/tornado/test/ioloop_test.py @@ -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 index 000000000..69c0ae33f --- /dev/null +++ b/tornado/test/netutil_test.py @@ -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) diff --git a/tornado/test/runtests.py b/tornado/test/runtests.py index cba692f21..90d94400f 100644 --- a/tornado/test/runtests.py +++ b/tornado/test/runtests.py @@ -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',