From 5b91cef24b882b9e95a9399d3c4fcd416f57a0ef Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Sun, 29 Jan 2023 13:11:52 +0000 Subject: [PATCH 01/14] Undeprecate ioloop make_current and clear_current methods --- tornado/ioloop.py | 24 +----------------------- tornado/platform/asyncio.py | 5 ----- 2 files changed, 1 insertion(+), 28 deletions(-) diff --git a/tornado/ioloop.py b/tornado/ioloop.py index 25609790d5..8245d34a2a 100644 --- a/tornado/ioloop.py +++ b/tornado/ioloop.py @@ -33,7 +33,6 @@ import time import math import random -import warnings from inspect import isawaitable from tornado.concurrent import ( @@ -123,8 +122,7 @@ async def main(): and instead initialize the `asyncio` event loop and use `IOLoop.current()`. In some cases, such as in test frameworks when initializing an `IOLoop` to be run in a secondary thread, it may be appropriate to construct - an `IOLoop` with ``IOLoop(make_current=False)``. Constructing an `IOLoop` - without the ``make_current=False`` argument is deprecated since Tornado 6.2. + an `IOLoop` with ``IOLoop(make_current=False)``. In general, an `IOLoop` cannot survive a fork or be shared across processes in any way. When multiple processes are being used, each process should @@ -144,13 +142,6 @@ async def main(): Uses the `asyncio` event loop by default. The ``IOLoop.configure`` method cannot be used on Python 3 except to redundantly specify the `asyncio` event loop. - - .. deprecated:: 6.2 - It is deprecated to create an event loop that is "current" but not - running. This means it is deprecated to pass - ``make_current=True`` to the ``IOLoop`` constructor, or to create - an ``IOLoop`` while no asyncio event loop is running unless - ``make_current=False`` is used. """ # These constants were originally based on constants from the epoll module. @@ -293,13 +284,6 @@ def make_current(self) -> None: .. versionchanged:: 5.0 This method also sets the current `asyncio` event loop. - - .. deprecated:: 6.2 - The concept of an event loop that is "current" without - currently running is deprecated in asyncio since Python - 3.10. All related functionality in Tornado is also - deprecated. Instead, start the event loop with `asyncio.run` - before interacting with it. """ # The asyncio event loops override this method. raise NotImplementedError() @@ -312,13 +296,7 @@ def clear_current() -> None: .. versionchanged:: 5.0 This method also clears the current `asyncio` event loop. - .. deprecated:: 6.2 """ - warnings.warn( - "clear_current is deprecated", - DeprecationWarning, - stacklevel=2, - ) IOLoop._clear_current() @staticmethod diff --git a/tornado/platform/asyncio.py b/tornado/platform/asyncio.py index a3fbdc37e3..779e339c97 100644 --- a/tornado/platform/asyncio.py +++ b/tornado/platform/asyncio.py @@ -328,11 +328,6 @@ def close(self, all_fds: bool = False) -> None: super().close(all_fds=all_fds) def make_current(self) -> None: - warnings.warn( - "make_current is deprecated; start the event loop first", - DeprecationWarning, - stacklevel=2, - ) if not self.is_current: try: self.old_asyncio = asyncio.get_event_loop() From 0b2190511c7edbb00b4768430e39b414418d0a22 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Sun, 29 Jan 2023 13:12:42 +0000 Subject: [PATCH 02/14] Undeprecate AsyncTestCase and AsyncHTTPTestCase classes --- tornado/testing.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/tornado/testing.py b/tornado/testing.py index c786a2e5cd..0232b219c7 100644 --- a/tornado/testing.py +++ b/tornado/testing.py @@ -162,17 +162,6 @@ def test_http_fetch(self): response = self.wait() # Test contents of response self.assertIn("FriendFeed", response.body) - - .. deprecated:: 6.2 - - AsyncTestCase and AsyncHTTPTestCase are deprecated due to changes - in future versions of Python (after 3.10). The interfaces used - in this class are incompatible with the deprecation and intended - removal of certain methods related to the idea of a "current" - event loop while no event loop is actually running. Use - `unittest.IsolatedAsyncioTestCase` instead. Note that this class - does not emit DeprecationWarnings until better migration guidance - can be provided. """ def __init__(self, methodName: str = "runTest") -> None: @@ -435,10 +424,6 @@ def test_homepage(self): like ``http_client.fetch()``, into a synchronous operation. If you need to do other asynchronous operations in tests, you'll probably need to use ``stop()`` and ``wait()`` yourself. - - .. deprecated:: 6.2 - `AsyncTestCase` and `AsyncHTTPTestCase` are deprecated due to changes - in Python 3.10; see comments on `AsyncTestCase` for more details. """ def setUp(self) -> None: From 1a4e1ddb6044c9c41190ef437adcda95be6a3b37 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Sun, 29 Jan 2023 13:13:12 +0000 Subject: [PATCH 03/14] Deprecation of bind/start is no longer forced by Python changes --- tornado/tcpserver.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tornado/tcpserver.py b/tornado/tcpserver.py index 183aac2177..deab8f2ad9 100644 --- a/tornado/tcpserver.py +++ b/tornado/tcpserver.py @@ -246,9 +246,7 @@ def bind( .. deprecated:: 6.2 Use either ``listen()`` or ``add_sockets()`` instead of ``bind()`` - and ``start()``. The ``bind()/start()`` pattern depends on - interfaces that have been deprecated in Python 3.10 and will be - removed in future versions of Python. + and ``start()``. """ sockets = bind_sockets( port, @@ -295,9 +293,7 @@ def start( .. deprecated:: 6.2 Use either ``listen()`` or ``add_sockets()`` instead of ``bind()`` - and ``start()``. The ``bind()/start()`` pattern depends on - interfaces that have been deprecated in Python 3.10 and will be - removed in future versions of Python. + and ``start()``. """ assert not self._started self._started = True From c8f27ee4996b258708d06795854b98ae0accf776 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Tue, 31 Jan 2023 11:09:14 +0000 Subject: [PATCH 04/14] IOLoop.current(): create new asyncio loop if not already present --- tornado/ioloop.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tornado/ioloop.py b/tornado/ioloop.py index 8245d34a2a..cad9853967 100644 --- a/tornado/ioloop.py +++ b/tornado/ioloop.py @@ -254,10 +254,13 @@ def current(instance: bool = True) -> Optional["IOLoop"]: # noqa: F811 """ try: loop = asyncio.get_event_loop() - except (RuntimeError, AssertionError): + except RuntimeError: if not instance: return None - raise + # Create a new asyncio event loop for this thread. + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + try: return IOLoop._ioloop_for_asyncio[loop] except KeyError: From f661b2a19358e1dde23fdd7fb740c74cfa78b375 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Fri, 10 Feb 2023 14:21:11 +0000 Subject: [PATCH 05/14] Re-simpify AsyncTestCase setUp & tearDown --- tornado/test/asyncio_test.py | 4 +++ tornado/testing.py | 50 +++--------------------------------- 2 files changed, 7 insertions(+), 47 deletions(-) diff --git a/tornado/test/asyncio_test.py b/tornado/test/asyncio_test.py index c33d7256b5..6ad1dcfbbe 100644 --- a/tornado/test/asyncio_test.py +++ b/tornado/test/asyncio_test.py @@ -30,6 +30,10 @@ def get_new_ioloop(self): io_loop = AsyncIOLoop(make_current=False) return io_loop + @property + def asyncio_loop(self): + return self.io_loop.asyncio_loop + def test_asyncio_callback(self): # Basic test that the asyncio loop is set up correctly. async def add_callback(): diff --git a/tornado/testing.py b/tornado/testing.py index 0232b219c7..508b1273f6 100644 --- a/tornado/testing.py +++ b/tornado/testing.py @@ -182,49 +182,9 @@ def __init__(self, methodName: str = "runTest") -> None: self._test_generator = None # type: Optional[Union[Generator, Coroutine]] def setUp(self) -> None: - setup_with_context_manager(self, warnings.catch_warnings()) - warnings.filterwarnings( - "ignore", - message="There is no current event loop", - category=DeprecationWarning, - module=r"tornado\..*", - ) super().setUp() - # NOTE: this code attempts to navigate deprecation warnings introduced - # in Python 3.10. The idea of an implicit current event loop is - # deprecated in that version, with the intention that tests like this - # explicitly create a new event loop and run on it. However, other - # packages such as pytest-asyncio (as of version 0.16.0) still rely on - # the implicit current event loop and we want to be compatible with them - # (even when run on 3.10, but not, of course, on the future version of - # python that removes the get/set_event_loop methods completely). - # - # Deprecation warnings were introduced inconsistently: - # asyncio.get_event_loop warns, but - # asyncio.get_event_loop_policy().get_event_loop does not. Similarly, - # none of the set_event_loop methods warn, although comments on - # https://bugs.python.org/issue39529 indicate that they are also - # intended for future removal. - # - # Therefore, we first attempt to access the event loop with the - # (non-warning) policy method, and if it fails, fall back to creating a - # new event loop. We do not have effective test coverage of the - # new event loop case; this will have to be watched when/if - # get_event_loop is actually removed. - self.should_close_asyncio_loop = False - try: - self.asyncio_loop = asyncio.get_event_loop_policy().get_event_loop() - except Exception: - self.asyncio_loop = asyncio.new_event_loop() - self.should_close_asyncio_loop = True - - async def get_loop() -> IOLoop: - return self.get_new_ioloop() - - self.io_loop = self.asyncio_loop.run_until_complete(get_loop()) - with warnings.catch_warnings(): - warnings.simplefilter("ignore", DeprecationWarning) - self.io_loop.make_current() + self.io_loop = self.get_new_ioloop() + self.io_loop.make_current() def tearDown(self) -> None: # Native coroutines tend to produce warnings if they're not @@ -259,17 +219,13 @@ def tearDown(self) -> None: # Clean up Subprocess, so it can be used again with a new ioloop. Subprocess.uninitialize() - with warnings.catch_warnings(): - warnings.simplefilter("ignore", DeprecationWarning) - self.io_loop.clear_current() + self.io_loop.clear_current() if not isinstance(self.io_loop, _NON_OWNED_IOLOOPS): # Try to clean up any file descriptors left open in the ioloop. # This avoids leaks, especially when tests are run repeatedly # in the same process with autoreload (because curl does not # set FD_CLOEXEC on its file descriptors) self.io_loop.close(all_fds=True) - if self.should_close_asyncio_loop: - self.asyncio_loop.close() super().tearDown() # In case an exception escaped or the StackContext caught an exception # when there wasn't a wait() to re-raise it, do so here. From 4bc84dca8b53f1657a1b7a2046308cbe357d3938 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Fri, 10 Feb 2023 14:39:37 +0000 Subject: [PATCH 06/14] Fix tests: IOLoop.current() now behaves the same way on any thread --- tornado/test/asyncio_test.py | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/tornado/test/asyncio_test.py b/tornado/test/asyncio_test.py index 6ad1dcfbbe..0b18ee5f96 100644 --- a/tornado/test/asyncio_test.py +++ b/tornado/test/asyncio_test.py @@ -183,25 +183,37 @@ def get_and_close_event_loop(): future = self.executor.submit(get_and_close_event_loop) return future.result() - def run_policy_test(self, accessor, expected_type): + def test_asyncio_accessor(self): with warnings.catch_warnings(): warnings.simplefilter("ignore", DeprecationWarning) # With the default policy, non-main threads don't get an event # loop. self.assertRaises( - (RuntimeError, AssertionError), self.executor.submit(accessor).result + RuntimeError, self.executor.submit(asyncio.get_event_loop).result ) # Set the policy and we can get a loop. asyncio.set_event_loop_policy(AnyThreadEventLoopPolicy()) self.assertIsInstance( - self.executor.submit(accessor).result(), expected_type + self.executor.submit(asyncio.get_event_loop).result(), asyncio.AbstractEventLoop ) # Clean up to silence leak warnings. Always use asyncio since # IOLoop doesn't (currently) close the underlying loop. self.executor.submit(lambda: asyncio.get_event_loop().close()).result() # type: ignore - def test_asyncio_accessor(self): - self.run_policy_test(asyncio.get_event_loop, asyncio.AbstractEventLoop) - def test_tornado_accessor(self): - self.run_policy_test(IOLoop.current, IOLoop) + # Tornado's IOLoop.current() API can create a loop for any thread, + # regardless of this event loop policy. + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + self.assertIsInstance( + self.executor.submit(IOLoop.current).result(), IOLoop + ) + # Clean up to silence leak warnings. Always use asyncio since + # IOLoop doesn't (currently) close the underlying loop. + self.executor.submit(lambda: asyncio.get_event_loop().close()).result() # type: ignore + + asyncio.set_event_loop_policy(AnyThreadEventLoopPolicy()) + self.assertIsInstance( + self.executor.submit(IOLoop.current).result(), IOLoop + ) + self.executor.submit(lambda: asyncio.get_event_loop().close()).result() # type: ignore From c23055d2cafb60763f64a4a3d4377668d1cfc23c Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Fri, 10 Feb 2023 15:24:35 +0000 Subject: [PATCH 07/14] Remove cleanup workaround to fix some ResourceWarnings --- tornado/test/asyncio_test.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tornado/test/asyncio_test.py b/tornado/test/asyncio_test.py index 0b18ee5f96..3d6bb39cc0 100644 --- a/tornado/test/asyncio_test.py +++ b/tornado/test/asyncio_test.py @@ -108,9 +108,6 @@ async def native_coroutine_with_adapter2(): self.asyncio_loop.run_until_complete(native_coroutine_with_adapter2()), 42, ) - # I'm not entirely sure why this manual cleanup is necessary but without - # it we have at-a-distance failures in ioloop_test.TestIOLoopCurrent. - asyncio.set_event_loop(None) class LeakTest(unittest.TestCase): From e4559b3024309135895721d98170ffdeb15d33e4 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Fri, 10 Feb 2023 17:11:54 +0000 Subject: [PATCH 08/14] Linter fixes --- tornado/test/asyncio_test.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/tornado/test/asyncio_test.py b/tornado/test/asyncio_test.py index 3d6bb39cc0..3a08ea494a 100644 --- a/tornado/test/asyncio_test.py +++ b/tornado/test/asyncio_test.py @@ -32,7 +32,7 @@ def get_new_ioloop(self): @property def asyncio_loop(self): - return self.io_loop.asyncio_loop + return self.io_loop.asyncio_loop # type: ignore def test_asyncio_callback(self): # Basic test that the asyncio loop is set up correctly. @@ -191,7 +191,8 @@ def test_asyncio_accessor(self): # Set the policy and we can get a loop. asyncio.set_event_loop_policy(AnyThreadEventLoopPolicy()) self.assertIsInstance( - self.executor.submit(asyncio.get_event_loop).result(), asyncio.AbstractEventLoop + self.executor.submit(asyncio.get_event_loop).result(), + asyncio.AbstractEventLoop, ) # Clean up to silence leak warnings. Always use asyncio since # IOLoop doesn't (currently) close the underlying loop. @@ -202,15 +203,11 @@ def test_tornado_accessor(self): # regardless of this event loop policy. with warnings.catch_warnings(): warnings.simplefilter("ignore", DeprecationWarning) - self.assertIsInstance( - self.executor.submit(IOLoop.current).result(), IOLoop - ) + self.assertIsInstance(self.executor.submit(IOLoop.current).result(), IOLoop) # Clean up to silence leak warnings. Always use asyncio since # IOLoop doesn't (currently) close the underlying loop. self.executor.submit(lambda: asyncio.get_event_loop().close()).result() # type: ignore asyncio.set_event_loop_policy(AnyThreadEventLoopPolicy()) - self.assertIsInstance( - self.executor.submit(IOLoop.current).result(), IOLoop - ) + self.assertIsInstance(self.executor.submit(IOLoop.current).result(), IOLoop) self.executor.submit(lambda: asyncio.get_event_loop().close()).result() # type: ignore From 6772f907f9bb09d9556205791053bcf4ca06dc48 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Fri, 10 Feb 2023 17:12:14 +0000 Subject: [PATCH 09/14] Re-suppress asyncio deprecation warnings in AsyncTestCase --- tornado/testing.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tornado/testing.py b/tornado/testing.py index 508b1273f6..cadb28beca 100644 --- a/tornado/testing.py +++ b/tornado/testing.py @@ -182,6 +182,13 @@ def __init__(self, methodName: str = "runTest") -> None: self._test_generator = None # type: Optional[Union[Generator, Coroutine]] def setUp(self) -> None: + setup_with_context_manager(self, warnings.catch_warnings()) + warnings.filterwarnings( + "ignore", + message="There is no current event loop", + category=DeprecationWarning, + module=r"tornado\..*", + ) super().setUp() self.io_loop = self.get_new_ioloop() self.io_loop.make_current() From fcef2a94bc93868897fe344d3604863ca60da192 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Wed, 15 Feb 2023 14:15:15 +0000 Subject: [PATCH 10/14] Re-deprecate make_current() & clear_current() methods --- tornado/ioloop.py | 11 +++++++++++ tornado/platform/asyncio.py | 5 +++++ tornado/testing.py | 8 ++++++-- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/tornado/ioloop.py b/tornado/ioloop.py index cad9853967..ce21ad44bd 100644 --- a/tornado/ioloop.py +++ b/tornado/ioloop.py @@ -33,6 +33,7 @@ import time import math import random +import warnings from inspect import isawaitable from tornado.concurrent import ( @@ -287,6 +288,10 @@ def make_current(self) -> None: .. versionchanged:: 5.0 This method also sets the current `asyncio` event loop. + + .. deprecated:: 6.2 + Setting and clearing the current event loop through Tornado is + deprecated. Use ``asyncio.set_event_loop`` instead if you need this. """ # The asyncio event loops override this method. raise NotImplementedError() @@ -299,7 +304,13 @@ def clear_current() -> None: .. versionchanged:: 5.0 This method also clears the current `asyncio` event loop. + .. deprecated:: 6.2 """ + warnings.warn( + "clear_current is deprecated", + DeprecationWarning, + stacklevel=2, + ) IOLoop._clear_current() @staticmethod diff --git a/tornado/platform/asyncio.py b/tornado/platform/asyncio.py index 779e339c97..a3fbdc37e3 100644 --- a/tornado/platform/asyncio.py +++ b/tornado/platform/asyncio.py @@ -328,6 +328,11 @@ def close(self, all_fds: bool = False) -> None: super().close(all_fds=all_fds) def make_current(self) -> None: + warnings.warn( + "make_current is deprecated; start the event loop first", + DeprecationWarning, + stacklevel=2, + ) if not self.is_current: try: self.old_asyncio = asyncio.get_event_loop() diff --git a/tornado/testing.py b/tornado/testing.py index cadb28beca..0ef097656a 100644 --- a/tornado/testing.py +++ b/tornado/testing.py @@ -190,8 +190,12 @@ def setUp(self) -> None: module=r"tornado\..*", ) super().setUp() + try: + self._prev_aio_loop = asyncio.get_event_loop() + except RuntimeError: + self._prev_aio_loop = None # type: ignore[assignment] self.io_loop = self.get_new_ioloop() - self.io_loop.make_current() + asyncio.set_event_loop(self.io_loop.asyncio_loop) # type: ignore[attr-defined] def tearDown(self) -> None: # Native coroutines tend to produce warnings if they're not @@ -226,7 +230,7 @@ def tearDown(self) -> None: # Clean up Subprocess, so it can be used again with a new ioloop. Subprocess.uninitialize() - self.io_loop.clear_current() + asyncio.set_event_loop(self._prev_aio_loop) if not isinstance(self.io_loop, _NON_OWNED_IOLOOPS): # Try to clean up any file descriptors left open in the ioloop. # This avoids leaks, especially when tests are run repeatedly From 4f40296bc3858d1d06358fe3c65bc6e850347685 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Wed, 15 Feb 2023 14:36:19 +0000 Subject: [PATCH 11/14] Deprecate IOLoop(make_current=True) --- tornado/ioloop.py | 19 +++++++++++++------ tornado/test/ioloop_test.py | 9 --------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/tornado/ioloop.py b/tornado/ioloop.py index ce21ad44bd..d698a97e10 100644 --- a/tornado/ioloop.py +++ b/tornado/ioloop.py @@ -143,6 +143,11 @@ async def main(): Uses the `asyncio` event loop by default. The ``IOLoop.configure`` method cannot be used on Python 3 except to redundantly specify the `asyncio` event loop. + + .. versionchanged:: 6.3 + ``make_current=False`` is now the default when creating an IOLoop - + previously the default was to make the event loop current if there wasn't + already a current one. Passing ``make_current=True`` is deprecated. """ # These constants were originally based on constants from the epoll module. @@ -268,7 +273,7 @@ def current(instance: bool = True) -> Optional["IOLoop"]: # noqa: F811 if instance: from tornado.platform.asyncio import AsyncIOMainLoop - current = AsyncIOMainLoop(make_current=True) # type: Optional[IOLoop] + current = AsyncIOMainLoop() # type: Optional[IOLoop] else: current = None return current @@ -336,11 +341,13 @@ def configurable_default(cls) -> Type[Configurable]: return AsyncIOLoop - def initialize(self, make_current: Optional[bool] = None) -> None: - if make_current is None: - if IOLoop.current(instance=False) is None: - self.make_current() - elif make_current: + def initialize(self, make_current: bool = False) -> None: + if make_current: + warnings.warn( + "IOLoop(make_current=True) is deprecated", + DeprecationWarning, + stacklevel=2, + ) current = IOLoop.current(instance=False) # AsyncIO loops can already be current by this point. if current is not None and current is not self: diff --git a/tornado/test/ioloop_test.py b/tornado/test/ioloop_test.py index 557ebc3757..a0ce493a9a 100644 --- a/tornado/test/ioloop_test.py +++ b/tornado/test/ioloop_test.py @@ -450,15 +450,6 @@ def tearDown(self): if self.io_loop is not None: self.io_loop.close() - def test_default_current(self): - self.io_loop = IOLoop() - # The first IOLoop with default arguments is made current. - self.assertIs(self.io_loop, IOLoop.current()) - # A second IOLoop can be created but is not made current. - io_loop2 = IOLoop() - self.assertIs(self.io_loop, IOLoop.current()) - io_loop2.close() - def test_non_current(self): self.io_loop = IOLoop(make_current=False) # The new IOLoop is not initially made current. From 85e395e659059b0194c575a85cb42065776d1a9b Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Thu, 16 Feb 2023 12:59:02 +0000 Subject: [PATCH 12/14] Default make_current -> True, remove check for existing event loop --- tornado/ioloop.py | 25 ++++++++++++------------- tornado/platform/asyncio.py | 9 ++------- tornado/test/ioloop_test.py | 5 ----- 3 files changed, 14 insertions(+), 25 deletions(-) diff --git a/tornado/ioloop.py b/tornado/ioloop.py index d698a97e10..bea2ca0b2c 100644 --- a/tornado/ioloop.py +++ b/tornado/ioloop.py @@ -145,9 +145,9 @@ async def main(): event loop. .. versionchanged:: 6.3 - ``make_current=False`` is now the default when creating an IOLoop - + ``make_current=True`` is now the default when creating an IOLoop - previously the default was to make the event loop current if there wasn't - already a current one. Passing ``make_current=True`` is deprecated. + already a current one. """ # These constants were originally based on constants from the epoll module. @@ -298,6 +298,14 @@ def make_current(self) -> None: Setting and clearing the current event loop through Tornado is deprecated. Use ``asyncio.set_event_loop`` instead if you need this. """ + warnings.warn( + "make_current is deprecated; start the event loop first", + DeprecationWarning, + stacklevel=2, + ) + self._make_current() + + def _make_current(self): # The asyncio event loops override this method. raise NotImplementedError() @@ -341,18 +349,9 @@ def configurable_default(cls) -> Type[Configurable]: return AsyncIOLoop - def initialize(self, make_current: bool = False) -> None: + def initialize(self, make_current: bool = True) -> None: if make_current: - warnings.warn( - "IOLoop(make_current=True) is deprecated", - DeprecationWarning, - stacklevel=2, - ) - 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() + self._make_current() def close(self, all_fds: bool = False) -> None: """Closes the `IOLoop`, freeing any resources used. diff --git a/tornado/platform/asyncio.py b/tornado/platform/asyncio.py index a3fbdc37e3..a9fa890047 100644 --- a/tornado/platform/asyncio.py +++ b/tornado/platform/asyncio.py @@ -276,7 +276,7 @@ class AsyncIOMainLoop(BaseAsyncIOLoop): def initialize(self, **kwargs: Any) -> None: # type: ignore super().initialize(asyncio.get_event_loop(), **kwargs) - def make_current(self) -> None: + def _make_current(self) -> None: # AsyncIOMainLoop already refers to the current asyncio loop so # nothing to do here. pass @@ -327,12 +327,7 @@ def close(self, all_fds: bool = False) -> None: self._clear_current() super().close(all_fds=all_fds) - def make_current(self) -> None: - warnings.warn( - "make_current is deprecated; start the event loop first", - DeprecationWarning, - stacklevel=2, - ) + def _make_current(self): if not self.is_current: try: self.old_asyncio = asyncio.get_event_loop() diff --git a/tornado/test/ioloop_test.py b/tornado/test/ioloop_test.py index a0ce493a9a..7de392f83e 100644 --- a/tornado/test/ioloop_test.py +++ b/tornado/test/ioloop_test.py @@ -472,11 +472,6 @@ def f(): def test_force_current(self): self.io_loop = IOLoop(make_current=True) self.assertIs(self.io_loop, IOLoop.current()) - with self.assertRaises(RuntimeError): - # A second make_current=True construction cannot succeed. - IOLoop(make_current=True) - # current() was not affected by the failed construction. - self.assertIs(self.io_loop, IOLoop.current()) class TestIOLoopCurrentAsync(AsyncTestCase): From eb9229d8824cad89f7a4ce2e95e07d22b89079e9 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Thu, 16 Feb 2023 13:16:36 +0000 Subject: [PATCH 13/14] Don't try to restore the previous event loop in AsyncTestCase --- tornado/test/testing_test.py | 28 ---------------------------- tornado/testing.py | 6 +----- 2 files changed, 1 insertion(+), 33 deletions(-) diff --git a/tornado/test/testing_test.py b/tornado/test/testing_test.py index ca48d567a2..cdbf1a3ae7 100644 --- a/tornado/test/testing_test.py +++ b/tornado/test/testing_test.py @@ -342,33 +342,5 @@ async def test(self): self.finished = True -class GetNewIOLoopTest(AsyncTestCase): - def get_new_ioloop(self): - # Use the current loop instead of creating a new one here. - return ioloop.IOLoop.current() - - def setUp(self): - # This simulates the effect of an asyncio test harness like - # pytest-asyncio. - with ignore_deprecation(): - try: - self.orig_loop = asyncio.get_event_loop() - except RuntimeError: - self.orig_loop = None # type: ignore[assignment] - self.new_loop = asyncio.new_event_loop() - asyncio.set_event_loop(self.new_loop) - super().setUp() - - def tearDown(self): - super().tearDown() - # AsyncTestCase must not affect the existing asyncio loop. - self.assertFalse(asyncio.get_event_loop().is_closed()) - asyncio.set_event_loop(self.orig_loop) - self.new_loop.close() - - def test_loop(self): - self.assertIs(self.io_loop.asyncio_loop, self.new_loop) # type: ignore - - if __name__ == "__main__": unittest.main() diff --git a/tornado/testing.py b/tornado/testing.py index 0ef097656a..68459f297a 100644 --- a/tornado/testing.py +++ b/tornado/testing.py @@ -190,10 +190,6 @@ def setUp(self) -> None: module=r"tornado\..*", ) super().setUp() - try: - self._prev_aio_loop = asyncio.get_event_loop() - except RuntimeError: - self._prev_aio_loop = None # type: ignore[assignment] self.io_loop = self.get_new_ioloop() asyncio.set_event_loop(self.io_loop.asyncio_loop) # type: ignore[attr-defined] @@ -230,7 +226,7 @@ def tearDown(self) -> None: # Clean up Subprocess, so it can be used again with a new ioloop. Subprocess.uninitialize() - asyncio.set_event_loop(self._prev_aio_loop) + asyncio.set_event_loop(None) if not isinstance(self.io_loop, _NON_OWNED_IOLOOPS): # Try to clean up any file descriptors left open in the ioloop. # This avoids leaks, especially when tests are run repeatedly From 65cd10a883c55bedb6ff0772ec2d6503ec39fcbe Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Thu, 16 Feb 2023 13:22:59 +0000 Subject: [PATCH 14/14] Linter fixes --- tornado/ioloop.py | 2 +- tornado/platform/asyncio.py | 2 +- tornado/test/testing_test.py | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/tornado/ioloop.py b/tornado/ioloop.py index bea2ca0b2c..bcdcca097b 100644 --- a/tornado/ioloop.py +++ b/tornado/ioloop.py @@ -305,7 +305,7 @@ def make_current(self) -> None: ) self._make_current() - def _make_current(self): + def _make_current(self) -> None: # The asyncio event loops override this method. raise NotImplementedError() diff --git a/tornado/platform/asyncio.py b/tornado/platform/asyncio.py index a9fa890047..a15a74df4d 100644 --- a/tornado/platform/asyncio.py +++ b/tornado/platform/asyncio.py @@ -327,7 +327,7 @@ def close(self, all_fds: bool = False) -> None: self._clear_current() super().close(all_fds=all_fds) - def _make_current(self): + def _make_current(self) -> None: if not self.is_current: try: self.old_asyncio = asyncio.get_event_loop() diff --git a/tornado/test/testing_test.py b/tornado/test/testing_test.py index cdbf1a3ae7..0429feee83 100644 --- a/tornado/test/testing_test.py +++ b/tornado/test/testing_test.py @@ -1,7 +1,6 @@ from tornado import gen, ioloop from tornado.httpserver import HTTPServer from tornado.locks import Event -from tornado.test.util import ignore_deprecation from tornado.testing import AsyncHTTPTestCase, AsyncTestCase, bind_unused_port, gen_test from tornado.web import Application import asyncio