IOLoop poller implementations are now subclasses of IOLoop.

Update testing command-line flags to allow configuration of a non-default
IOLoop.
This commit is contained in:
Ben Darnell 2012-09-30 22:57:32 -07:00
parent 91c5eb83f9
commit 469e227402
5 changed files with 74 additions and 38 deletions

View File

@ -103,7 +103,20 @@ class IOLoop(Configurable):
@classmethod @classmethod
def configurable_default(cls): 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 # Constants from the epoll module
_EPOLLIN = 0x001 _EPOLLIN = 0x001
@ -126,8 +139,8 @@ class IOLoop(Configurable):
_current = threading.local() _current = threading.local()
def initialize(self, impl=None): def initialize(self, impl):
self._impl = impl or _poll() self._impl = impl
if hasattr(self._impl, 'fileno'): if hasattr(self._impl, 'fileno'):
set_close_exec(self._impl.fileno()) set_close_exec(self._impl.fileno())
self._handlers = {} self._handlers = {}
@ -643,6 +656,8 @@ class _EPoll(object):
_EPOLL_CTL_MOD = 3 _EPOLL_CTL_MOD = 3
def __init__(self): def __init__(self):
from tornado import epoll
self.epoll = epoll
self._epoll_fd = epoll.epoll_create() self._epoll_fd = epoll.epoll_create()
def fileno(self): def fileno(self):
@ -652,16 +667,21 @@ class _EPoll(object):
os.close(self._epoll_fd) os.close(self._epoll_fd)
def register(self, fd, events): 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): 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): 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): 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): class _KQueue(object):
@ -728,6 +748,11 @@ class _KQueue(object):
return events.items() return events.items()
class KQueueIOLoop(IOLoop):
def initialize(self, **kwargs):
super(KQueueIOLoop, self).initialize(impl=_KQueue(), **kwargs)
class _Select(object): class _Select(object):
"""A simple, select()-based IOLoop implementation for non-Linux systems""" """A simple, select()-based IOLoop implementation for non-Linux systems"""
def __init__(self): def __init__(self):
@ -774,23 +799,11 @@ class _Select(object):
events[fd] = events.get(fd, 0) | IOLoop.ERROR events[fd] = events.get(fd, 0) | IOLoop.ERROR
return events.items() 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 class EPollIOLoop(IOLoop):
if hasattr(select, "epoll"): def initialize(self, **kwargs):
# Python 2.6+ on Linux super(EPollIOLoop, self).initialize(impl=select.epoll(), **kwargs)
_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

View File

@ -4,6 +4,9 @@ from __future__ import absolute_import, division, with_statement
import logging import logging
import textwrap import textwrap
import sys import sys
from tornado.httpclient import AsyncHTTPClient
from tornado.ioloop import IOLoop
from tornado.options import define
from tornado.test.util import unittest from tornado.test.util import unittest
TEST_MODULES = [ TEST_MODULES = [
@ -76,6 +79,11 @@ if __name__ == '__main__':
logging.getLogger("tornado.access").setLevel(logging.CRITICAL) 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 import tornado.testing
kwargs = {} kwargs = {}
if sys.version_info >= (3, 2): if sys.version_info >= (3, 2):

View File

@ -471,9 +471,6 @@ def main(**kwargs):
""" """
from tornado.options import define, options, parse_command_line 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, define('exception_on_interrupt', type=bool, default=True,
help=("If true (default), ctrl-c raises a KeyboardInterrupt " help=("If true (default), ctrl-c raises a KeyboardInterrupt "
"exception. This prints a stack trace but cannot interrupt " "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) 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: if not options.exception_on_interrupt:
signal.signal(signal.SIGINT, signal.SIG_DFL) signal.signal(signal.SIGINT, signal.SIG_DFL)
@ -526,11 +519,7 @@ def main(**kwargs):
gen_log.info('PASS') gen_log.info('PASS')
else: else:
gen_log.error('FAIL') gen_log.error('FAIL')
if not options.autoreload:
raise raise
if options.autoreload:
import tornado.autoreload
tornado.autoreload.wait()
if __name__ == '__main__': if __name__ == '__main__':
main() main()

11
tox.ini
View File

@ -74,6 +74,17 @@ deps =
twisted>=11.1.0 twisted>=11.1.0
commands = python -m tornado.test.runtests --httpclient=tornado.curl_httpclient.CurlAsyncHTTPClient {posargs:} 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] [testenv:pypy-full]
# This configuration works with pypy 1.9. pycurl installs ok but # This configuration works with pypy 1.9. pycurl installs ok but
# curl_httpclient doesn't work. Twisted works most of the time, but # curl_httpclient doesn't work. Twisted works most of the time, but

View File

@ -104,3 +104,18 @@ In progress
argument. argument.
* Keyword arguments to `AsyncHTTPClient.configure` are no longer used * Keyword arguments to `AsyncHTTPClient.configure` are no longer used
when instantiating an implementation subclass directly. 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`.