diff --git a/maint/vm/ubuntu12.04/tox.ini b/maint/vm/ubuntu12.04/tox.ini index 4098f883..fdf6f1f9 100644 --- a/maint/vm/ubuntu12.04/tox.ini +++ b/maint/vm/ubuntu12.04/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py27-full, py25-full, py32, py25, py27 +envlist = py27-full, py25-full, py32, py25, py27, py27-select setupdir=/tornado toxworkdir=/home/vagrant/tox-tornado @@ -27,4 +27,12 @@ basepython = python2.7 deps = futures pycurl - twisted==12.2.0 \ No newline at end of file + twisted==12.2.0 + +[testenv:py27-select] +basepython = python2.7 +deps = + futures + pycurl + twisted==12.2.0 +commands = python -m tornado.test.runtests --ioloop=tornado.platform.select.SelectIOLoop {posargs:} diff --git a/tornado/ioloop.py b/tornado/ioloop.py index 1c7cddee..2e1c65e8 100644 --- a/tornado/ioloop.py +++ b/tornado/ioloop.py @@ -35,6 +35,7 @@ import heapq import logging import os import select +import sys import thread import threading import time @@ -103,20 +104,19 @@ class IOLoop(Configurable): @classmethod def configurable_default(cls): - 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: + if hasattr(select, "epoll") or sys.platform.startswith('linux'): try: - # Python 2.5 on Linux with our C module installed - from tornado import epoll - return EPoll25IOLoop - except Exception: - # Everything else - return SelectIOLoop + from tornado.platform.epoll import EPollIOLoop + return EPollIOLoop + except ImportError: + gen_log.warning("unable to import EPollIOLoop, falling back to SelectIOLoop") + pass + if hasattr(select, "kqueue"): + # Python 2.6+ on BSD or Mac + from tornado.platform.kqueue import KQueueIOLoop + return KQueueIOLoop + from tornado.platform.select import SelectIOLoop + return SelectIOLoop # Constants from the epoll module _EPOLLIN = 0x001 @@ -663,163 +663,3 @@ class PeriodicCallback(object): while self._next_timeout <= current_time: self._next_timeout += self.callback_time / 1000.0 self._timeout = self.io_loop.add_timeout(self._next_timeout, self._run) - - -class _EPoll(object): - """An epoll-based event loop using our C module for Python 2.5 systems""" - _EPOLL_CTL_ADD = 1 - _EPOLL_CTL_DEL = 2 - _EPOLL_CTL_MOD = 3 - - def __init__(self): - from tornado import epoll - self.epoll = epoll - self._epoll_fd = epoll.epoll_create() - - def fileno(self): - return self._epoll_fd - - def close(self): - os.close(self._epoll_fd) - - def register(self, fd, events): - self.epoll.epoll_ctl(self._epoll_fd, self._EPOLL_CTL_ADD, fd, events) - - def modify(self, fd, events): - self.epoll.epoll_ctl(self._epoll_fd, self._EPOLL_CTL_MOD, fd, events) - - def unregister(self, fd): - self.epoll.epoll_ctl(self._epoll_fd, self._EPOLL_CTL_DEL, fd, 0) - - def poll(self, timeout): - 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): - """A kqueue-based event loop for BSD/Mac systems.""" - def __init__(self): - self._kqueue = select.kqueue() - self._active = {} - - def fileno(self): - return self._kqueue.fileno() - - def close(self): - self._kqueue.close() - - def register(self, fd, events): - if fd in self._active: - raise IOError("fd %d already registered" % fd) - self._control(fd, events, select.KQ_EV_ADD) - self._active[fd] = events - - def modify(self, fd, events): - self.unregister(fd) - self.register(fd, events) - - def unregister(self, fd): - events = self._active.pop(fd) - self._control(fd, events, select.KQ_EV_DELETE) - - def _control(self, fd, events, flags): - kevents = [] - if events & IOLoop.WRITE: - kevents.append(select.kevent( - fd, filter=select.KQ_FILTER_WRITE, flags=flags)) - if events & IOLoop.READ or not kevents: - # Always read when there is not a write - kevents.append(select.kevent( - fd, filter=select.KQ_FILTER_READ, flags=flags)) - # Even though control() takes a list, it seems to return EINVAL - # on Mac OS X (10.6) when there is more than one event in the list. - for kevent in kevents: - self._kqueue.control([kevent], 0) - - def poll(self, timeout): - kevents = self._kqueue.control(None, 1000, timeout) - events = {} - for kevent in kevents: - fd = kevent.ident - if kevent.filter == select.KQ_FILTER_READ: - events[fd] = events.get(fd, 0) | IOLoop.READ - if kevent.filter == select.KQ_FILTER_WRITE: - if kevent.flags & select.KQ_EV_EOF: - # If an asynchronous connection is refused, kqueue - # returns a write event with the EOF flag set. - # Turn this into an error for consistency with the - # other IOLoop implementations. - # Note that for read events, EOF may be returned before - # all data has been consumed from the socket buffer, - # so we only check for EOF on write events. - events[fd] = IOLoop.ERROR - else: - events[fd] = events.get(fd, 0) | IOLoop.WRITE - if kevent.flags & select.KQ_EV_ERROR: - events[fd] = events.get(fd, 0) | IOLoop.ERROR - 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): - self.read_fds = set() - self.write_fds = set() - self.error_fds = set() - self.fd_sets = (self.read_fds, self.write_fds, self.error_fds) - - def close(self): - pass - - def register(self, fd, events): - if fd in self.read_fds or fd in self.write_fds or fd in self.error_fds: - raise IOError("fd %d already registered" % fd) - if events & IOLoop.READ: - self.read_fds.add(fd) - if events & IOLoop.WRITE: - self.write_fds.add(fd) - if events & IOLoop.ERROR: - self.error_fds.add(fd) - # Closed connections are reported as errors by epoll and kqueue, - # but as zero-byte reads by select, so when errors are requested - # we need to listen for both read and error. - self.read_fds.add(fd) - - def modify(self, fd, events): - self.unregister(fd) - self.register(fd, events) - - def unregister(self, fd): - self.read_fds.discard(fd) - self.write_fds.discard(fd) - self.error_fds.discard(fd) - - def poll(self, timeout): - readable, writeable, errors = select.select( - self.read_fds, self.write_fds, self.error_fds, timeout) - events = {} - for fd in readable: - events[fd] = events.get(fd, 0) | IOLoop.READ - for fd in writeable: - events[fd] = events.get(fd, 0) | IOLoop.WRITE - for fd in errors: - 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) - - -class EPollIOLoop(IOLoop): - def initialize(self, **kwargs): - super(EPollIOLoop, self).initialize(impl=select.epoll(), **kwargs) diff --git a/tornado/platform/epoll.py b/tornado/platform/epoll.py new file mode 100644 index 00000000..5a6af222 --- /dev/null +++ b/tornado/platform/epoll.py @@ -0,0 +1,68 @@ +#!/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. +"""EPoll-based IOLoop implementation for Linux systems. + +Supports the standard library's `select.epoll` function for Python 2.6+, +and our own C module for Python 2.5. +""" +from __future__ import absolute_import, division, with_statement + +import os +import select + +from tornado.ioloop import IOLoop + +if hasattr(select, 'epoll'): + # Python 2.6+ + class EPollIOLoop(IOLoop): + def initialize(self, **kwargs): + super(EPollIOLoop, self).initialize(impl=select.epoll(), **kwargs) +else: + # Python 2.5 + from tornado import epoll + + class _EPoll(object): + """An epoll-based event loop using our C module for Python 2.5 systems""" + _EPOLL_CTL_ADD = 1 + _EPOLL_CTL_DEL = 2 + _EPOLL_CTL_MOD = 3 + + def __init__(self): + self._epoll_fd = epoll.epoll_create() + + def fileno(self): + return self._epoll_fd + + def close(self): + os.close(self._epoll_fd) + + def register(self, fd, events): + 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) + + def unregister(self, fd): + 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)) + + + class EPollIOLoop(IOLoop): + def initialize(self, **kwargs): + super(EPollIOLoop, self).initialize(impl=_EPoll(), **kwargs) + diff --git a/tornado/platform/kqueue.py b/tornado/platform/kqueue.py new file mode 100644 index 00000000..45f4cc9d --- /dev/null +++ b/tornado/platform/kqueue.py @@ -0,0 +1,91 @@ +#!/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. +"""KQueue-based IOLoop implementation for BSD/Mac systems.""" +from __future__ import absolute_import, division, with_statement + +import select + +from tornado.ioloop import IOLoop + +assert hasattr(select, 'kqueue'), 'kqueue not supported' + +class _KQueue(object): + """A kqueue-based event loop for BSD/Mac systems.""" + def __init__(self): + self._kqueue = select.kqueue() + self._active = {} + + def fileno(self): + return self._kqueue.fileno() + + def close(self): + self._kqueue.close() + + def register(self, fd, events): + if fd in self._active: + raise IOError("fd %d already registered" % fd) + self._control(fd, events, select.KQ_EV_ADD) + self._active[fd] = events + + def modify(self, fd, events): + self.unregister(fd) + self.register(fd, events) + + def unregister(self, fd): + events = self._active.pop(fd) + self._control(fd, events, select.KQ_EV_DELETE) + + def _control(self, fd, events, flags): + kevents = [] + if events & IOLoop.WRITE: + kevents.append(select.kevent( + fd, filter=select.KQ_FILTER_WRITE, flags=flags)) + if events & IOLoop.READ or not kevents: + # Always read when there is not a write + kevents.append(select.kevent( + fd, filter=select.KQ_FILTER_READ, flags=flags)) + # Even though control() takes a list, it seems to return EINVAL + # on Mac OS X (10.6) when there is more than one event in the list. + for kevent in kevents: + self._kqueue.control([kevent], 0) + + def poll(self, timeout): + kevents = self._kqueue.control(None, 1000, timeout) + events = {} + for kevent in kevents: + fd = kevent.ident + if kevent.filter == select.KQ_FILTER_READ: + events[fd] = events.get(fd, 0) | IOLoop.READ + if kevent.filter == select.KQ_FILTER_WRITE: + if kevent.flags & select.KQ_EV_EOF: + # If an asynchronous connection is refused, kqueue + # returns a write event with the EOF flag set. + # Turn this into an error for consistency with the + # other IOLoop implementations. + # Note that for read events, EOF may be returned before + # all data has been consumed from the socket buffer, + # so we only check for EOF on write events. + events[fd] = IOLoop.ERROR + else: + events[fd] = events.get(fd, 0) | IOLoop.WRITE + if kevent.flags & select.KQ_EV_ERROR: + events[fd] = events.get(fd, 0) | IOLoop.ERROR + return events.items() + + +class KQueueIOLoop(IOLoop): + def initialize(self, **kwargs): + super(KQueueIOLoop, self).initialize(impl=_KQueue(), **kwargs) diff --git a/tornado/platform/select.py b/tornado/platform/select.py new file mode 100644 index 00000000..a8788aca --- /dev/null +++ b/tornado/platform/select.py @@ -0,0 +1,75 @@ +#!/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. +"""Select-based IOLoop implementation. + +Used as a fallback for systems that don't support epoll or kqueue. +""" +from __future__ import absolute_import, division, with_statement + +import select + +from tornado.ioloop import IOLoop + +class _Select(object): + """A simple, select()-based IOLoop implementation for non-Linux systems""" + def __init__(self): + self.read_fds = set() + self.write_fds = set() + self.error_fds = set() + self.fd_sets = (self.read_fds, self.write_fds, self.error_fds) + + def close(self): + pass + + def register(self, fd, events): + if fd in self.read_fds or fd in self.write_fds or fd in self.error_fds: + raise IOError("fd %d already registered" % fd) + if events & IOLoop.READ: + self.read_fds.add(fd) + if events & IOLoop.WRITE: + self.write_fds.add(fd) + if events & IOLoop.ERROR: + self.error_fds.add(fd) + # Closed connections are reported as errors by epoll and kqueue, + # but as zero-byte reads by select, so when errors are requested + # we need to listen for both read and error. + self.read_fds.add(fd) + + def modify(self, fd, events): + self.unregister(fd) + self.register(fd, events) + + def unregister(self, fd): + self.read_fds.discard(fd) + self.write_fds.discard(fd) + self.error_fds.discard(fd) + + def poll(self, timeout): + readable, writeable, errors = select.select( + self.read_fds, self.write_fds, self.error_fds, timeout) + events = {} + for fd in readable: + events[fd] = events.get(fd, 0) | IOLoop.READ + for fd in writeable: + events[fd] = events.get(fd, 0) | IOLoop.WRITE + for fd in errors: + 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) + diff --git a/tox.ini b/tox.ini index 9525e795..9adf8b53 100644 --- a/tox.ini +++ b/tox.ini @@ -83,7 +83,7 @@ deps = futures pycurl twisted>=12.0.0 -commands = python -m tornado.test.runtests --ioloop=tornado.ioloop.SelectIOLoop {posargs:} +commands = python -m tornado.test.runtests --ioloop=tornado.platform.select.SelectIOLoop {posargs:} [testenv:py27-monotonic] basepython = python2.7