From dc7b58d8fe115bff41258dc5c80e28f03082b480 Mon Sep 17 00:00:00 2001 From: Ben Darnell Date: Sat, 3 Jun 2017 16:08:44 -0400 Subject: [PATCH] ioloop: Make asyncio the default when available In addition to changing the configurable default, add a special case in IOLoop.current() so that the current IOLoop will be backed by the main asyncio event loop. --- .travis.yml | 5 ++--- tornado/ioloop.py | 16 +++++++++++++++- tornado/test/httpclient_test.py | 9 ++------- tornado/test/ioloop_test.py | 21 +++++++++++++++------ tornado/test/process_test.py | 21 +++++++++++++++------ tox.ini | 8 ++++---- 6 files changed, 53 insertions(+), 27 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4b2ac2bcc..d2b549211 100644 --- a/.travis.yml +++ b/.travis.yml @@ -61,13 +61,12 @@ script: - if [[ $TRAVIS_PYTHON_VERSION == 3* ]]; then python -bb $TARGET; fi - if [[ $TRAVIS_PYTHON_VERSION != pypy* ]]; then python $TARGET --resolver=tornado.netutil.ThreadedResolver; fi - if [[ $TRAVIS_PYTHON_VERSION == 2* ]]; then python $TARGET --httpclient=tornado.curl_httpclient.CurlAsyncHTTPClient; fi - - if [[ $TRAVIS_PYTHON_VERSION == 2* ]]; then python $TARGET --ioloop_time_monotonic; fi - if [[ $TRAVIS_PYTHON_VERSION != 'pypy'* ]]; then python $TARGET --ioloop=tornado.platform.twisted.TwistedIOLoop; fi - - if [[ $TRAVIS_PYTHON_VERSION == 3.4 || $TRAVIS_PYTHON_VERSION == 3.5 || $TRAVIS_PYTHON_VERSION == 3.6 ]]; then python $TARGET --ioloop=tornado.platform.asyncio.AsyncIOLoop; fi + - if [[ $TRAVIS_PYTHON_VERSION == 3.4 || $TRAVIS_PYTHON_VERSION == 3.5 || $TRAVIS_PYTHON_VERSION == 3.6 ]]; then python $TARGET --ioloop=tornado.ioloop.PollIOLoop; fi - if [[ $TRAVIS_PYTHON_VERSION == 2* ]]; then python $TARGET --ioloop=tornado.platform.asyncio.AsyncIOLoop; fi - if [[ $TRAVIS_PYTHON_VERSION == 2* ]]; then python $TARGET --resolver=tornado.platform.twisted.TwistedResolver; fi + - if [[ $TRAVIS_PYTHON_VERSION != pypy* ]]; then python $TARGET --ioloop=tornado.ioloop.PollIOLoop --ioloop_time_monotonic; fi #- if [[ $TRAVIS_PYTHON_VERSION != pypy* ]]; then python $TARGET --resolver=tornado.platform.caresresolver.CaresResolver; fi - - if [[ $TRAVIS_PYTHON_VERSION == 3* ]]; then python $TARGET --ioloop_time_monotonic; fi - if [[ $TRAVIS_PYTHON_VERSION != 'pypy3' ]]; then ../nodeps/bin/python -m tornado.test.runtests; fi # make coverage reports for Codecov to find - if [[ $TRAVIS_PYTHON_VERSION != nightly ]]; then coverage xml; fi diff --git a/tornado/ioloop.py b/tornado/ioloop.py index 46a60fb36..0527e9222 100644 --- a/tornado/ioloop.py +++ b/tornado/ioloop.py @@ -61,6 +61,11 @@ if PY3: else: import thread +try: + import asyncio +except ImportError: + asyncio = None + _POLL_TIMEOUT = 3600.0 @@ -242,7 +247,13 @@ class IOLoop(Configurable): """ current = getattr(IOLoop._current, "instance", None) if current is None and instance: - current = IOLoop() + current = None + if asyncio is not None: + from tornado.platform.asyncio import AsyncIOLoop, AsyncIOMainLoop + if IOLoop.configured_class() is AsyncIOLoop: + current = AsyncIOMainLoop() + if current is None: + current = IOLoop() if IOLoop._current.instance is not current: raise RuntimeError("new IOLoop did not become current") return current @@ -276,6 +287,9 @@ class IOLoop(Configurable): @classmethod def configurable_default(cls): + if asyncio is not None: + from tornado.platform.asyncio import AsyncIOLoop + return AsyncIOLoop return PollIOLoop def initialize(self, make_current=None): diff --git a/tornado/test/httpclient_test.py b/tornado/test/httpclient_test.py index 1fa42b7ee..8281f39aa 100644 --- a/tornado/test/httpclient_test.py +++ b/tornado/test/httpclient_test.py @@ -582,16 +582,11 @@ class HTTPResponseTestCase(unittest.TestCase): class SyncHTTPClientTest(unittest.TestCase): def setUp(self): - if IOLoop.configured_class().__name__ in ('TwistedIOLoop', - 'AsyncIOMainLoop'): + if IOLoop.configured_class().__name__ == 'TwistedIOLoop': # TwistedIOLoop only supports the global reactor, so we can't have # separate IOLoops for client and server threads. - # AsyncIOMainLoop doesn't work with the default policy - # (although it could with some tweaks to this test and a - # policy that created loops for non-main threads). raise unittest.SkipTest( - 'Sync HTTPClient not compatible with TwistedIOLoop or ' - 'AsyncIOMainLoop') + 'Sync HTTPClient not compatible with TwistedIOLoop') self.server_ioloop = IOLoop() @gen.coroutine diff --git a/tornado/test/ioloop_test.py b/tornado/test/ioloop_test.py index 318f3fad0..942e33c2c 100644 --- a/tornado/test/ioloop_test.py +++ b/tornado/test/ioloop_test.py @@ -275,7 +275,9 @@ class TestIOLoop(AsyncTestCase): self.io_loop.call_later(0, results.append, 4) self.io_loop.call_later(0, self.stop) self.wait() - self.assertEqual(results, [1, 2, 3, 4]) + # The asyncio event loop does not guarantee the order of these + # callbacks, but PollIOLoop does. + self.assertEqual(sorted(results), [1, 2, 3, 4]) def test_add_timeout_return(self): # All the timeout methods return non-None handles that can be @@ -699,10 +701,17 @@ class TestIOLoopConfiguration(unittest.TestCase): return native_str(subprocess.check_output(args)).strip() def test_default(self): - # The default is a subclass of PollIOLoop - is_poll = self.run_python( - 'print(isinstance(IOLoop.current(), PollIOLoop))') - self.assertEqual(is_poll, 'True') + if asyncio is not None: + # When asyncio is available, it is used by default. + cls = self.run_python('print(classname(IOLoop.current()))') + self.assertEqual(cls, 'AsyncIOMainLoop') + cls = self.run_python('print(classname(IOLoop()))') + self.assertEqual(cls, 'AsyncIOLoop') + else: + # Otherwise, the default is a subclass of PollIOLoop + is_poll = self.run_python( + 'print(isinstance(IOLoop.current(), PollIOLoop))') + self.assertEqual(is_poll, 'True') def test_explicit_select(self): # SelectIOLoop can always be configured explicitly. @@ -716,7 +725,7 @@ class TestIOLoopConfiguration(unittest.TestCase): cls = self.run_python( 'IOLoop.configure("tornado.platform.asyncio.AsyncIOLoop")', 'print(classname(IOLoop.current()))') - self.assertEqual(cls, 'AsyncIOLoop') + self.assertEqual(cls, 'AsyncIOMainLoop') @unittest.skipIf(asyncio is None, "asyncio module not present") def test_asyncio_main(self): diff --git a/tornado/test/process_test.py b/tornado/test/process_test.py index d8337f5d5..8497a7d8f 100644 --- a/tornado/test/process_test.py +++ b/tornado/test/process_test.py @@ -17,12 +17,15 @@ from tornado.testing import bind_unused_port, ExpectLog, AsyncTestCase, gen_test from tornado.test.util import unittest, skipIfNonUnix from tornado.web import RequestHandler, Application +try: + import asyncio +except ImportError: + asyncio = None + def skip_if_twisted(): - if IOLoop.configured_class().__name__.endswith(('TwistedIOLoop', - 'AsyncIOMainLoop')): - raise unittest.SkipTest("Process tests not compatible with " - "TwistedIOLoop or AsyncIOMainLoop") + 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. @@ -58,8 +61,10 @@ class ProcessTest(unittest.TestCase): super(ProcessTest, self).tearDown() def test_multi_process(self): - # This test can't work on twisted because we use the global reactor - # and have no way to get it back into a sane state after the fork. + # This test doesn't work on twisted because we use the global + # reactor and don't restore it to a sane state after the fork + # (asyncio has the same issue, but we have a special case in + # place for it). skip_if_twisted() with ExpectLog(gen_log, "(Starting .* processes|child .* exited|uncaught exception)"): self.assertFalse(IOLoop.initialized()) @@ -81,6 +86,10 @@ class ProcessTest(unittest.TestCase): sock.close() return try: + if asyncio is not None: + # Reset the global asyncio event loop, which was put into + # a broken state by the fork. + asyncio.set_event_loop(asyncio.new_event_loop()) if id in (0, 1): self.assertEqual(id, task_id()) server = HTTPServer(self.get_app()) diff --git a/tox.ini b/tox.ini index 9e472ae85..409f025e7 100644 --- a/tox.ini +++ b/tox.ini @@ -30,7 +30,7 @@ envlist = {py2,py3}-select, {py2,py3}-full-twisted, py2-twistedlayered, - {py3,py33}-full-asyncio, + py3-full-poll, py2-full-trollius, # Alternate Resolvers. @@ -84,7 +84,6 @@ deps = {py2,py27,pypy,py3,pypy3}-full: mock # singledispatch became standard in py34. {py2,py27,pypy,py3,py33}-full: singledispatch - py33-asyncio: asyncio trollius: trollius py2-monotonic: monotonic sphinx: sphinx @@ -120,13 +119,14 @@ commands = # implementations; this flag controls which client all the # other tests use. curl: --httpclient=tornado.curl_httpclient.CurlAsyncHTTPClient \ + poll: --ioloop=tornado.ioloop.PollIOLoop \ select: --ioloop=tornado.platform.select.SelectIOLoop \ twisted: --ioloop=tornado.platform.twisted.TwistedIOLoop \ twistedlayered: --ioloop=tornado.test.twisted_test.LayeredTwistedIOLoop --resolver=tornado.platform.twisted.TwistedResolver \ - {asyncio,trollius}: --ioloop=tornado.platform.asyncio.AsyncIOLoop \ + trollius: --ioloop=tornado.platform.asyncio.AsyncIOLoop \ caresresolver: --resolver=tornado.platform.caresresolver.CaresResolver \ threadedresolver: --resolver=tornado.netutil.ThreadedResolver \ - monotonic: --ioloop_time_monotonic \ + monotonic: --ioloop=tornado.ioloop.PollIOLoop --ioloop_time_monotonic \ # Test with a non-english locale to uncover str/bytes mixing issues. locale: --locale=zh_TW \ {posargs:} -- 2.47.2