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

asyncio: Use a canary task to detect end of event loop #3272

Merged
merged 1 commit into from
Jun 19, 2023

Conversation

bdarnell
Copy link
Member

Attempt to clean up our selector thread (on windows) when the event loop shuts down without waiting for GC. We can't always do this, but we can at least detect it for asyncio.run. For cases where we do need to rely on GC, we now rely on the GC of the canary Task instead of the AddThreadSelectorEventLoop.

Fixes #3173

@bdarnell
Copy link
Member Author

#3173 suggested handling the CancelledError and GeneratorExit branches separately, but simply calling self.close() both places seems to work for me. It's possible that the fact that GeneratorExit is raised in a GC/destructor context means that we may be in a shutdown scenario in which other runtime features aren't working, so we'll need to watch for that. (and be aware that this is only on Windows, so it's not going to be tested as much)

@bdarnell
Copy link
Member Author

Hmm, the GeneratorExit code path appears to always log "coroutine was never awaited" too. Maybe we should keep the old destructor and add a thread join there, and leave the canary task to only listen for clean cancellation.

Async generators have a special shutdown protocol which allows
us to detect the end of the event loop and stop our thread.
This lets us clean up the thread reliably when the event loop
is started/stopped via the tornado IOLoop interfaces (which
explicitly know about the selector thread), or when the
latest asyncio interfaces are used (asyncio.run or manually
calling shutdown_asyncgens).

The thread is still leaked when older versions of the asyncio
interfaces are used (loop.close *without* shutdown_asyncgens), but
I've been unable to find a solution that does not print leak warnings
even in the event of a clean shutdown. Use of shutdown_asyncgens is
now effectively required for apps combining asyncio and tornado.
This is unfortunate since leaking a thread is relatively expensive
compared to the usual consequences of failing to call
shutdown_asyncgens, but it seems to be the best we can do.

Fixes tornadoweb#3173
@bdarnell
Copy link
Member Author

I'm now taking a different approach than the original version of this change: asynchronous generators have a formal, documented shutdown procedure so we can use one to detect the shutdown of the event loop when newer interfaces like asyncio.run are used. This is less fragile than the original attempt to have a task get cancelled.

Unfortunately I was never able to find a good GC-based solution for cases when the shutdown_asyncgens method is not called, so we'll still leak the thread in that case. The solution if that becomes a problem will be to add shutdown_asyncgens calls as appropriate (which will also help with other cleanup in async generators).

@bdarnell bdarnell merged commit c3b8ee8 into tornadoweb:master Jun 19, 2023
10 checks passed
@bdarnell bdarnell deleted the asyncio-canary branch June 19, 2023 17:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

use a canary task to detect asyncio.run shutdown - and join selector thread there
1 participant