Check if main event loop is running before scheduling
Fixes #525
After some debugging, I found that main_event_loop is sometimes closed. If it's closed, we should not call_soon_threadsafe
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.
It's already "failing to stop" now due to a potential deadlock, do you have another solution?
Is there a reason the answer is any more complicated than “don’t call EventLoop.stop if you want it to keep running”?
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...]
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.
<_UnixSelectorEventLoop running=False closed=False debug=False>
I misinterpreted. It's actually not closed but neither running.
I see, if loop is not running, loop.create_task will never create a task and it'll just seem getting stuck