Permalink
Browse files

Add a test that runs TwistedIOLoop on top of TornadoReactor.

This exposed a few more small bugs.
  • Loading branch information...
1 parent 57130df commit 0e44baf70056f67ded16b12a04f2cbc87fe054cd @bdarnell bdarnell committed Feb 23, 2013
Showing with 64 additions and 12 deletions.
  1. +15 −8 tornado/platform/twisted.py
  2. +1 −1 tornado/test/process_test.py
  3. +36 −0 tornado/test/twisted_test.py
  4. +12 −3 tox.ini
@@ -186,8 +186,12 @@ def _removeDelayedCall(self, dc):
def callFromThread(self, f, *args, **kw):
"""See `twisted.internet.interfaces.IReactorThreads.callFromThread`"""
assert callable(f), "%s is not callable" % f
- p = functools.partial(f, *args, **kw)
- self._io_loop.add_callback(p)
+ with NullContext():
+ # This NullContext is mainly for an edge case when running
+ # TwistedIOLoop on top of a TornadoReactor.
+ # TwistedIOLoop.add_callback uses reactor.callFromThread and
+ # should not pick up additional StackContexts along the way.
+ self._io_loop.add_callback(f, *args, **kw)
# We don't need the waker code from the super class, Tornado uses
# its own waker.
@@ -392,16 +396,18 @@ def logPrefix(self):
class TwistedIOLoop(tornado.ioloop.IOLoop):
"""IOLoop implementation that runs on Twisted.
- Uses the global Twisted reactor. It is possible to create multiple
- TwistedIOLoops in the same process, but it doesn't really make sense
- because they will all run in the same thread.
+ Uses the global Twisted reactor by default. To create multiple
+ `TwistedIOLoops` in the same process, you must pass a unique reactor
+ when constructing each one.
Not compatible with `tornado.process.Subprocess.set_exit_callback`
because the ``SIGCHLD`` handlers used by Tornado and Twisted conflict
with each other.
"""
- def initialize(self):
- from twisted.internet import reactor
+ def initialize(self, reactor=None):
+ if reactor is None:
+ import twisted.internet.reactor
+ reactor = twisted.internet.reactor
self.reactor = reactor
self.fds = {}
@@ -471,7 +477,8 @@ def add_timeout(self, deadline, callback):
return self.reactor.callLater(delay, self._run_callback, wrap(callback))
def remove_timeout(self, timeout):
- timeout.cancel()
+ if timeout.active():
+ timeout.cancel()
def add_callback(self, callback, *args, **kwargs):
self.reactor.callFromThread(self._run_callback,
@@ -19,7 +19,7 @@
def skip_if_twisted():
- if IOLoop.configured_class().__name__ == 'TwistedIOLoop':
+ if IOLoop.configured_class().__name__.endswith('TwistedIOLoop'):
raise unittest.SkipTest("Process tests not compatible with TwistedIOLoop")
# Not using AsyncHTTPTestCase because we need control over the IOLoop.
@@ -56,6 +56,7 @@
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from tornado.platform.auto import set_close_exec
+from tornado.platform.select import SelectIOLoop
from tornado.testing import bind_unused_port
from tornado.test.util import unittest
from tornado.util import import_object
@@ -571,6 +572,41 @@ def unbuildReactor(self, reactor):
log.defaultObserver.stop()
# import sys; log.startLogging(sys.stderr, setStdout=0)
# log.startLoggingWithObserver(log.PythonLoggingObserver().emit, setStdout=0)
+ # import logging; logging.getLogger('twisted').setLevel(logging.WARNING)
+
+if have_twisted:
+ class LayeredTwistedIOLoop(TwistedIOLoop):
+ """Layers a TwistedIOLoop on top of a TornadoReactor on a SelectIOLoop.
+
+ This is of course silly, but is useful for testing purposes to make
+ sure we're implementing both sides of the various interfaces
+ correctly. In some tests another TornadoReactor is layered on top
+ of the whole stack.
+ """
+ def initialize(self):
+ # When configured to use LayeredTwistedIOLoop we can't easily
+ # get the next-best IOLoop implementation, so use the lowest common
+ # denominator.
+ self.real_io_loop = SelectIOLoop()
+ reactor = TornadoReactor(io_loop=self.real_io_loop)
+ super(LayeredTwistedIOLoop, self).initialize(reactor=reactor)
+
+ def close(self, all_fds=False):
+ super(LayeredTwistedIOLoop, self).close(all_fds=all_fds)
+ # HACK: This is the same thing that test_class.unbuildReactor does.
+ for reader in self.reactor._internalReaders:
+ self.reactor.removeReader(reader)
+ reader.connectionLost(None)
+ self.real_io_loop.close(all_fds=all_fds)
+
+ def stop(self):
+ # One of twisted's tests fails if I don't delay crash()
+ # until the reactor has started, but if I move this to
+ # TwistedIOLoop then the tests fail when I'm *not* running
+ # tornado-on-twisted-on-tornado. I'm clearly missing something
+ # about the startup/crash semantics, but since stop and crash
+ # are really only used in tests it doesn't really matter.
+ self.reactor.callWhenRunning(self.reactor.crash)
if __name__ == "__main__":
unittest.main()
View
@@ -11,7 +11,7 @@
[tox]
# "-full" variants include optional dependencies, to ensure
# that things work both in a bare install and with all the extras.
-envlist = py27-full, py27-curl, py32-full, pypy, py26, py26-full, py27, py32, py32-utf8, py33, py27-opt, py32-opt, pypy-full, py27-select, py27-monotonic, py33-monotonic, py27-twisted, py27-threadedresolver, py27-twistedresolver
+envlist = py27-full, py27-curl, py32-full, pypy, py26, py26-full, py27, py32, py32-utf8, py33, py27-opt, py32-opt, pypy-full, py27-select, py27-monotonic, py33-monotonic, py27-twisted, py27-threadedresolver, py27-twistedresolver, py27-twistedlayered
[testenv]
commands = python -m tornado.test.runtests {posargs:}
@@ -98,7 +98,7 @@ deps =
mock
pycurl
twisted
-commands = python -m tornado.test.runtests --resolver=tornado.netutil.ThreadedResolver
+commands = python -m tornado.test.runtests --resolver=tornado.netutil.ThreadedResolver {posargs:}
[testenv:py27-twistedresolver]
basepython = python2.7
@@ -107,7 +107,16 @@ deps =
mock
pycurl
twisted
-commands = python -m tornado.test.runtests --resolver=tornado.platform.twisted.TwistedResolver
+commands = python -m tornado.test.runtests --resolver=tornado.platform.twisted.TwistedResolver {posargs:}
+
+[testenv:py27-twistedlayered]
+basepython = python2.7
+deps =
+ futures
+ mock
+ pycurl
+ twisted
+commands = python -m tornado.test.runtests --ioloop=tornado.test.twisted_test.LayeredTwistedIOLoop --resolver=tornado.platform.twisted.TwistedResolver {posargs:}
[testenv:pypy-full]
# This configuration works with pypy 1.9. pycurl installs ok but

0 comments on commit 0e44baf

Please sign in to comment.