Perhaps it's good to distinguish between graceful shutdown signal (cancel all head/logical tasks, or even all tasks, let finally blocks run) and hard stop signal.
In the past, synchronous code, I've used following paradigm: def custom_signal(): alarm(5) raise KeyboardInterrupt() Keyboard interrupt was chosen so that manual execution is stopped with ^C in the same way server process, this makes testing much easier :) Also it inherits from BaseException, which is nice. I think that something similar can be done for you asynchronous case -- graceful shutdown using asyncio builtin signal handling and hard stop using signal.SIG_DFL and signal number where that means termination. On 25 April 2018 at 09:54, Nathaniel Smith <n...@pobox.com> wrote: > On Tue, Apr 24, 2018 at 2:25 PM, Mark E. Haase <meha...@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 > _______________________________________________ > Async-sig mailing list > Async-sig@python.org > https://mail.python.org/mailman/listinfo/async-sig > Code of Conduct: https://www.python.org/psf/codeofconduct/ _______________________________________________ Async-sig mailing list Async-sig@python.org https://mail.python.org/mailman/listinfo/async-sig Code of Conduct: https://www.python.org/psf/codeofconduct/