asgiref icon indicating copy to clipboard operation
asgiref copied to clipboard

Check if main event loop is running before scheduling

Open kigawas opened this issue 3 months ago • 7 comments

Fixes #525

After some debugging, I found that main_event_loop is sometimes closed. If it's closed, we should not call_soon_threadsafe

kigawas avatar Sep 09 '25 19:09 kigawas

If the user called EventLoop.stop, we should respect their choice. We shouldn’t overreact by silently creating a new event loop in a new thread—that’s going to create confusing bugs with async objects associated with the wrong loop, thread unsafety, and programs failing to stop.

andersk avatar Sep 09 '25 19:09 andersk

It's already "failing to stop" now due to a potential deadlock, do you have another solution?

kigawas avatar Sep 10 '25 02:09 kigawas

Is there a reason the answer is any more complicated than “don’t call EventLoop.stop if you want it to keep running”?

andersk avatar Sep 10 '25 02:09 andersk

In my case, EventLoop.stop was not called explicitly, the event loop just stopped somewhere

[TEST1] main event loop <_UnixSelectorEventLoop running=True closed=False debug=False>
in main event loop, running? True
exc_info (None, None, None) awaitable <coroutine object convert_exception_to_response.<locals>.inner at 0x123d415d0>
result <HttpResponse status_code=401, "application/json">
running_in_main_event_loop True
PASSED
[TEST2] main event loop <_UnixSelectorEventLoop running=True closed=False debug=False>
in main event loop, running? True
exc_info (None, None, None) awaitable <coroutine object convert_exception_to_response.<locals>.inner at 0x122c91f30>
result <HttpResponse status_code=200, "application/json; charset=utf-8">
running_in_main_event_loop True
main event loop <_UnixSelectorEventLoop running=False closed=False debug=False>
in main event loop, running? False
[...stuck...]

kigawas avatar Sep 10 '25 03:09 kigawas

Event loops don’t arbitrarily stop themselves. You can look at the code here:

https://github.com/python/cpython/blob/v3.13.7/Lib/asyncio/base_events.py

The only way for BaseEventLoop.is_running() to change from True to False is if BaseEventLoop._thread_id is set to None, which only happens in BaseEventLoop._run_forever_cleanup, which is only called by BaseEventLoop.run_forever if BaseEventLoop._stopping became True, which only happens if BaseEventLoop.stop() was called. (Perhaps by another library function like BaseEventLoop.run_until_complete → _run_until_complete_cb → BaseEventLoop.stop, although it would be weird to be calling both run_forever and run_until_complete on the same event loop.)

Without a reproducible test case, there’s not really anything more I can say.

andersk avatar Sep 10 '25 03:09 andersk

<_UnixSelectorEventLoop running=False closed=False debug=False>

I misinterpreted. It's actually not closed but neither running.

kigawas avatar Sep 10 '25 10:09 kigawas

I see, if loop is not running, loop.create_task will never create a task and it'll just seem getting stuck

kigawas avatar Sep 10 '25 18:09 kigawas