Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow passing asyncio_loop argument to AsyncIOLoop #3157

Merged
merged 3 commits into from
Jun 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
25 changes: 21 additions & 4 deletions tornado/platform/asyncio.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,14 @@ def initialize( # type: ignore
del IOLoop._ioloop_for_asyncio[loop]
except KeyError:
pass

# Make sure we don't already have an IOLoop for this asyncio loop
if asyncio_loop in IOLoop._ioloop_for_asyncio:
existing_loop = IOLoop._ioloop_for_asyncio[asyncio_loop]
raise RuntimeError(
f"IOLoop {existing_loop} already associated with asyncio loop {asyncio_loop}"
)

IOLoop._ioloop_for_asyncio[asyncio_loop] = self

self._thread_identity = 0
Expand Down Expand Up @@ -269,7 +277,7 @@ def run_in_executor(
self,
executor: Optional[concurrent.futures.Executor],
func: Callable[..., _T],
*args: Any
*args: Any,
) -> Awaitable[_T]:
return self.asyncio_loop.run_in_executor(executor, func, *args)

Expand Down Expand Up @@ -310,6 +318,12 @@ class AsyncIOLoop(BaseAsyncIOLoop):
Each ``AsyncIOLoop`` creates a new ``asyncio.EventLoop``; this object
can be accessed with the ``asyncio_loop`` attribute.

.. versionchanged:: 6.2
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if you want this in 6.2 or hold for 6.3. I think it's likely to be useful for folks like me trying to deal with the new deprecations, though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should definitely go into 6.2 if we do it at all.


Support explicit ``asyncio_loop`` argument
for specifying the asyncio loop to attach to,
rather than always creating a new one with the default policy.

.. versionchanged:: 5.0

When an ``AsyncIOLoop`` becomes the current `.IOLoop`, it also sets
Expand All @@ -323,13 +337,16 @@ class AsyncIOLoop(BaseAsyncIOLoop):

def initialize(self, **kwargs: Any) -> None: # type: ignore
self.is_current = False
loop = asyncio.new_event_loop()
loop = None
if "asyncio_loop" not in kwargs:
kwargs["asyncio_loop"] = loop = asyncio.new_event_loop()
try:
super().initialize(loop, **kwargs)
super().initialize(**kwargs)
except Exception:
# If initialize() does not succeed (taking ownership of the loop),
# we have to close it.
loop.close()
if loop is not None:
loop.close()
raise

def close(self, all_fds: bool = False) -> None:
Expand Down
10 changes: 10 additions & 0 deletions tornado/test/ioloop_test.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import asyncio
from concurrent.futures import ThreadPoolExecutor
from concurrent import futures
from collections.abc import Generator
Expand Down Expand Up @@ -427,6 +428,15 @@ def f():

yield gen.multi([self.io_loop.run_in_executor(None, f) for i in range(2)])

def test_explicit_asyncio_loop(self):
asyncio_loop = asyncio.new_event_loop()
loop = IOLoop(asyncio_loop=asyncio_loop, make_current=False)
assert loop.asyncio_loop is asyncio_loop # type: ignore
with self.assertRaises(RuntimeError):
# Can't register two IOLoops with the same asyncio_loop
IOLoop(asyncio_loop=asyncio_loop, make_current=False)
loop.close()


# Deliberately not a subclass of AsyncTestCase so the IOLoop isn't
# automatically set as current.
Expand Down