From 469e227402a0b5374a640e2639bca7354de17701 Mon Sep 17 00:00:00 2001 From: Ben Darnell Date: Sun, 30 Sep 2012 22:57:32 -0700 Subject: [PATCH] IOLoop poller implementations are now subclasses of IOLoop. Update testing command-line flags to allow configuration of a non-default IOLoop. --- tornado/ioloop.py | 65 +++++++++++++++++++------------- tornado/test/runtests.py | 8 ++++ tornado/testing.py | 13 +------ tox.ini | 11 ++++++ website/sphinx/releases/next.rst | 15 ++++++++ 5 files changed, 74 insertions(+), 38 deletions(-) diff --git a/tornado/ioloop.py b/tornado/ioloop.py index 29e0edb6..f2d84480 100644 --- a/tornado/ioloop.py +++ b/tornado/ioloop.py @@ -103,7 +103,20 @@ class IOLoop(Configurable): @classmethod def configurable_default(cls): - return IOLoop + if hasattr(select, "epoll"): + # Python 2.6+ on Linux + return EPollIOLoop + elif hasattr(select, "kqueue"): + # Python 2.6+ on BSD or Mac + return KQueueIOLoop + else: + try: + # Python 2.5 on Linux with our C module installed + from tornado import epoll + return EPoll25IOLoop + except Exception: + # Everything else + return SelectIOLoop # Constants from the epoll module _EPOLLIN = 0x001 @@ -126,8 +139,8 @@ class IOLoop(Configurable): _current = threading.local() - def initialize(self, impl=None): - self._impl = impl or _poll() + def initialize(self, impl): + self._impl = impl if hasattr(self._impl, 'fileno'): set_close_exec(self._impl.fileno()) self._handlers = {} @@ -643,6 +656,8 @@ class _EPoll(object): _EPOLL_CTL_MOD = 3 def __init__(self): + from tornado import epoll + self.epoll = epoll self._epoll_fd = epoll.epoll_create() def fileno(self): @@ -652,16 +667,21 @@ class _EPoll(object): os.close(self._epoll_fd) def register(self, fd, events): - epoll.epoll_ctl(self._epoll_fd, self._EPOLL_CTL_ADD, fd, events) + self.epoll.epoll_ctl(self._epoll_fd, self._EPOLL_CTL_ADD, fd, events) def modify(self, fd, events): - epoll.epoll_ctl(self._epoll_fd, self._EPOLL_CTL_MOD, fd, events) + self.epoll.epoll_ctl(self._epoll_fd, self._EPOLL_CTL_MOD, fd, events) def unregister(self, fd): - epoll.epoll_ctl(self._epoll_fd, self._EPOLL_CTL_DEL, fd, 0) + self.epoll.epoll_ctl(self._epoll_fd, self._EPOLL_CTL_DEL, fd, 0) def poll(self, timeout): - return epoll.epoll_wait(self._epoll_fd, int(timeout * 1000)) + return self.epoll.epoll_wait(self._epoll_fd, int(timeout * 1000)) + + +class EPoll25IOLoop(IOLoop): + def initialize(self, **kwargs): + super(EPoll25IOLoop, self).initialize(impl=_EPoll(), **kwargs) class _KQueue(object): @@ -728,6 +748,11 @@ class _KQueue(object): return events.items() +class KQueueIOLoop(IOLoop): + def initialize(self, **kwargs): + super(KQueueIOLoop, self).initialize(impl=_KQueue(), **kwargs) + + class _Select(object): """A simple, select()-based IOLoop implementation for non-Linux systems""" def __init__(self): @@ -774,23 +799,11 @@ class _Select(object): events[fd] = events.get(fd, 0) | IOLoop.ERROR return events.items() +class SelectIOLoop(IOLoop): + def initialize(self, **kwargs): + super(SelectIOLoop, self).initialize(impl=_Select(), **kwargs) -# Choose a poll implementation. Use epoll if it is available, fall back to -# select() for non-Linux platforms -if hasattr(select, "epoll"): - # Python 2.6+ on Linux - _poll = select.epoll -elif hasattr(select, "kqueue"): - # Python 2.6+ on BSD or Mac - _poll = _KQueue -else: - try: - # Linux systems with our C module installed - from tornado import epoll - _poll = _EPoll - except Exception: - # All other systems - import sys - if "linux" in sys.platform: - gen_log.warning("epoll module not found; using select()") - _poll = _Select + +class EPollIOLoop(IOLoop): + def initialize(self, **kwargs): + super(EPollIOLoop, self).initialize(impl=select.epoll(), **kwargs) diff --git a/tornado/test/runtests.py b/tornado/test/runtests.py index 1f8e43ea..103df58e 100644 --- a/tornado/test/runtests.py +++ b/tornado/test/runtests.py @@ -4,6 +4,9 @@ from __future__ import absolute_import, division, with_statement import logging import textwrap import sys +from tornado.httpclient import AsyncHTTPClient +from tornado.ioloop import IOLoop +from tornado.options import define from tornado.test.util import unittest TEST_MODULES = [ @@ -76,6 +79,11 @@ if __name__ == '__main__': logging.getLogger("tornado.access").setLevel(logging.CRITICAL) + define('httpclient', type=str, default=None, + callback=AsyncHTTPClient.configure) + define('ioloop', type=str, default=None, + callback=IOLoop.configure) + import tornado.testing kwargs = {} if sys.version_info >= (3, 2): diff --git a/tornado/testing.py b/tornado/testing.py index 34aa1b89..b34b6dac 100644 --- a/tornado/testing.py +++ b/tornado/testing.py @@ -471,9 +471,6 @@ def main(**kwargs): """ from tornado.options import define, options, parse_command_line - define('autoreload', type=bool, default=False, - help="DEPRECATED: use tornado.autoreload.main instead") - define('httpclient', type=str, default=None) define('exception_on_interrupt', type=bool, default=True, help=("If true (default), ctrl-c raises a KeyboardInterrupt " "exception. This prints a stack trace but cannot interrupt " @@ -489,10 +486,6 @@ def main(**kwargs): argv = [sys.argv[0]] + parse_command_line(sys.argv) - if options.httpclient: - from tornado.httpclient import AsyncHTTPClient - AsyncHTTPClient.configure(options.httpclient) - if not options.exception_on_interrupt: signal.signal(signal.SIGINT, signal.SIG_DFL) @@ -526,11 +519,7 @@ def main(**kwargs): gen_log.info('PASS') else: gen_log.error('FAIL') - if not options.autoreload: - raise - if options.autoreload: - import tornado.autoreload - tornado.autoreload.wait() + raise if __name__ == '__main__': main() diff --git a/tox.ini b/tox.ini index 203550cb..9fb6fc11 100644 --- a/tox.ini +++ b/tox.ini @@ -74,6 +74,17 @@ deps = twisted>=11.1.0 commands = python -m tornado.test.runtests --httpclient=tornado.curl_httpclient.CurlAsyncHTTPClient {posargs:} +[testenv:py27-select] +# Same as py27-full, but runs the tests with the select IOLoop. +# The other tests will run with the most platform-appropriate implementation, +# but this one is the lowest common denominator and should work anywhere. +basepython = python2.7 +deps = + futures + pycurl + twisted>=12.0.0 +commands = python -m tornado.test.runtests --ioloop=tornado.ioloop.SelectIOLoop {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 diff --git a/website/sphinx/releases/next.rst b/website/sphinx/releases/next.rst index 4ff63115..f4e8bbaa 100644 --- a/website/sphinx/releases/next.rst +++ b/website/sphinx/releases/next.rst @@ -104,3 +104,18 @@ In progress argument. * Keyword arguments to `AsyncHTTPClient.configure` are no longer used when instantiating an implementation subclass directly. +* The `IOLoop` poller implementations (``select``, ``epoll``, ``kqueue``) + are now available as distinct subclasses of `IOLoop`. Instantiating + `IOLoop` will continue to automatically choose the best available + implementation. +* `IOLoop` now has a static ``configure`` method like the one on + `AsyncHTTPClient`, which can be used to select an IOLoop implementation + other than the default. +* The deprecated ``--autoreload`` option of `tornado.testing.main` has + been removed. Use ``python -m tornado.autoreload`` as a prefix command + instead. +* The ``--httpclient`` option of `tornado.testing.main` has been moved + to `tornado.test.runtests` so as not to pollute the application + option namespace. The `tornado.options` module's new callback + support now makes it easy to add options from a wrapper script + instead of putting all possible options in `tornado.testing.main`.