From: Ben Darnell Date: Sun, 21 Jan 2018 18:51:59 +0000 (-0500) Subject: ioloop: current() and friends delegate to asyncio X-Git-Tag: v5.0.0~12^2~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=df541dccb69d8196d2871ba2addf85f1515f84c1;p=thirdparty%2Ftornado.git ioloop: current() and friends delegate to asyncio Instead of a redundant IOLoop._current thread-local, pass through directly to asyncio and maintain a one-to-one mapping of asyncio loops to IOLoops. This brings us a bit closer to the asyncio-only future. --- diff --git a/tornado/ioloop.py b/tornado/ioloop.py index b17f24bd9..2e09b32b9 100644 --- a/tornado/ioloop.py +++ b/tornado/ioloop.py @@ -47,6 +47,7 @@ import threading import time import traceback import math +import weakref from tornado.concurrent import Future, is_future, chain_future, future_set_exc_info, future_add_done_callback # noqa: E501 from tornado.log import app_log, gen_log @@ -180,11 +181,12 @@ class IOLoop(Configurable): WRITE = _EPOLLOUT ERROR = _EPOLLERR | _EPOLLHUP - # Global lock for creating global IOLoop instance - _instance_lock = threading.Lock() - + # In Python 2, _current.instance points to the current IOLoop. _current = threading.local() + # In Python 3, _ioloop_for_asyncio maps from asyncio loops to IOLoops. + _ioloop_for_asyncio = weakref.WeakKeyDictionary() + @classmethod def configure(cls, impl, **kwargs): if asyncio is not None: @@ -232,7 +234,15 @@ class IOLoop(Configurable): This method only knows about `IOLoop` objects (and not, for example, `asyncio` event loops), so it is of limited use. """ - return IOLoop.current(instance=False) is not None + if asyncio is None: + return IOLoop.current(instance=False) is not None + else: + # Asyncio gives us no way to ask whether an instance + # exists without creating one. Even calling + # `IOLoop.current(instance=False)` will create the asyncio + # loop (but not the Tornado loop, which would break the + # ability to use this function for "is it safe to fork?". + return False def install(self): """Deprecated alias for `make_current()`. @@ -276,22 +286,36 @@ class IOLoop(Configurable): Added ``instance`` argument to control the fallback to `IOLoop.instance()`. .. versionchanged:: 5.0 + On Python 3, control of the current `IOLoop` is delegated + to `asyncio`, with this and other methods as pass-through accessors. The ``instance`` argument now controls whether an `IOLoop` is created automatically when there is none, instead of whether we fall back to `IOLoop.instance()` (which is now - an alias for this method) + an alias for this method). ``instance=False`` is deprecated, + since even if we do not create an `IOLoop`, this method + may initialize the asyncio loop. """ - current = getattr(IOLoop._current, "instance", None) - if current is None and instance: - 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: + if asyncio is None: + current = getattr(IOLoop._current, "instance", None) + if current is None and instance: current = IOLoop() - if IOLoop._current.instance is not current: - raise RuntimeError("new IOLoop did not become current") + if IOLoop._current.instance is not current: + raise RuntimeError("new IOLoop did not become current") + else: + try: + loop = asyncio.get_event_loop() + except RuntimeError: + if not instance: + return None + raise + try: + return IOLoop._ioloop_for_asyncio[loop] + except KeyError: + if instance: + from tornado.platform.asyncio import AsyncIOMainLoop + current = AsyncIOMainLoop(make_current=True) + else: + current = None return current def make_current(self): @@ -310,6 +334,8 @@ class IOLoop(Configurable): .. versionchanged:: 5.0 This method also sets the current `asyncio` event loop. """ + # The asyncio event loops override this method. + assert asyncio is None old = getattr(IOLoop._current, "instance", None) if old is not None: old.clear_current() @@ -324,10 +350,11 @@ class IOLoop(Configurable): .. versionchanged:: 5.0 This method also clears the current `asyncio` event loop. """ - old = getattr(IOLoop._current, "instance", None) + old = IOLoop.current(instance=False) if old is not None: old._clear_current_hook() - IOLoop._current.instance = None + if asyncio is None: + IOLoop._current.instance = None def _clear_current_hook(self): """Instance method called when an IOLoop ceases to be current. @@ -352,7 +379,9 @@ class IOLoop(Configurable): if IOLoop.current(instance=False) is None: self.make_current() elif make_current: - if IOLoop.current(instance=False) is not None: + current = IOLoop.current(instance=False) + # AsyncIO loops can already be current by this point. + if current is not None and current is not self: raise RuntimeError("current IOLoop already exists") self.make_current() diff --git a/tornado/platform/asyncio.py b/tornado/platform/asyncio.py index d28a491db..0d7eea3e5 100644 --- a/tornado/platform/asyncio.py +++ b/tornado/platform/asyncio.py @@ -38,6 +38,7 @@ class BaseAsyncIOLoop(IOLoop): self.readers = set() self.writers = set() self.closing = False + IOLoop._ioloop_for_asyncio[asyncio_loop] = self super(BaseAsyncIOLoop, self).initialize(**kwargs) def close(self, all_fds=False): @@ -101,16 +102,16 @@ class BaseAsyncIOLoop(IOLoop): handler_func(fileobj, events) def start(self): - old_current = IOLoop.current(instance=False) + try: + old_loop = asyncio.get_event_loop() + except RuntimeError: + old_loop = None try: self._setup_logging() - self.make_current() + asyncio.set_event_loop(self.asyncio_loop) self.asyncio_loop.run_forever() finally: - if old_current is None: - IOLoop.clear_current() - else: - old_current.make_current() + asyncio.set_event_loop(old_loop) def stop(self): self.asyncio_loop.stop() @@ -165,6 +166,11 @@ class AsyncIOMainLoop(BaseAsyncIOLoop): def initialize(self, **kwargs): super(AsyncIOMainLoop, self).initialize(asyncio.get_event_loop(), **kwargs) + def make_current(self): + # AsyncIOMainLoop already refers to the current asyncio loop so + # nothing to do here. + pass + class AsyncIOLoop(BaseAsyncIOLoop): """``AsyncIOLoop`` is an `.IOLoop` that runs on an ``asyncio`` event loop. @@ -203,7 +209,6 @@ class AsyncIOLoop(BaseAsyncIOLoop): def make_current(self): if not self.is_current: - super(AsyncIOLoop, self).make_current() try: self.old_asyncio = asyncio.get_event_loop() except RuntimeError: diff --git a/tornado/test/ioloop_test.py b/tornado/test/ioloop_test.py index 7cae9f1ab..1aa3f1e54 100644 --- a/tornado/test/ioloop_test.py +++ b/tornado/test/ioloop_test.py @@ -725,6 +725,8 @@ class TestIOLoopRunSync(unittest.TestCase): self.io_loop.run_sync(namespace['f']) +@unittest.skipIf(asyncio is not None, + 'IOLoop configuration not available') class TestPeriodicCallback(unittest.TestCase): def setUp(self): self.io_loop = FakeTimeIOLoop() @@ -830,6 +832,8 @@ class TestIOLoopConfiguration(unittest.TestCase): self.assertEqual(cls, 'AsyncIOMainLoop') @unittest.skipIf(twisted is None, "twisted module not present") + @unittest.skipIf(asyncio is not None, + "IOLoop configuration not available") def test_twisted(self): cls = self.run_python( 'from tornado.platform.twisted import TwistedIOLoop',