On Tue, Apr 24, 2018 at 2:25 PM, Mark E. Haase <mehaase@gmail.com> wrote:
My mental model of how the event loop works is pretty poor, but I roughly understand that the event loop is responsible for driving coroutines. It appears here that the event loop has stopped driving my main() coroutine, and so the only way to force it to complete is to call send() from my code.
It hasn't stopped driving your main() coroutine – as far as it knows, main() is still waiting for the Event.wait() call to complete, and as soon as it does it will start iterating the coroutine again. You really, really, definitely should not be trying to manually iterate a coroutine object associate with a Task.
More broadly, handling KeyboardInterrupt in async code seems very tricky, but I also cannot figure out how to make this interrupt approach work. Is one of these better than the other? What is the best practice here? Would it be terrible to add `while True: main_coro.send(None)` to my signal handler?
Yes, it would be terrible :-). Instead of trying to throw exceptions manually, you should call the cancel() method on the Task object. (Of if you want to abort immediately because the previous control-C was ignored, use something like os._exit() or os.abort().) The other complication is that doing *anything* from a signal handler is fraught with peril, because of reentrancy issues. I actually don't think there are *any* functions in asyncio that are guaranteed to be safe to call from a signal handler. Looking at the code for Task.cancel, I definitely don't trust that it's safe to call from a signal handler. The simplest solution would be to use asyncio's native signal handler support instead of the signal module: https://docs.python.org/3/library/asyncio-eventloop.html#unix-signals However, there are some trade-offs: - it's not implemented on Windows - it relies on the event loop running. In particular, if the event loop is stalled (e.g. because some task got stuck in an infinite loop), then your signal handler will never be called, so your "emergency abort" code won't work. Alternatively, you can define a handler using signal.signal, and then arrange to re-enter the asyncio main loop yourself before calling Task.cancel. I believe that the only guaranteed-to-be-safe way to do this is: - in your signal handler, spawn a new thread (!) - from the new thread, call loop.call_soon_threadsafe(your_main_task.cancel) (Trio's version of call_soon_threadsafe *is* guaranteed to be both thread- and signal-safe, but asyncio's isn't, and in asyncio there are multiple event loop implementations so even if one happens to be signal-safe by chance you don't know about the others... also Trio handles control-C automatically so you don't need to worry about this in the first place. But I don't know how to port Trio's generic solution to asyncio :-(.) -n -- Nathaniel J. Smith -- https://vorpus.org