[Async-sig] Cancelling a coroutine from a signal handler?

Dima Tisnek dimaqq at gmail.com
Tue Apr 24 22:33:17 EDT 2018


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 <njs at pobox.com> wrote:
> On Tue, Apr 24, 2018 at 2:25 PM, Mark E. Haase <mehaase at 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 at python.org
> https://mail.python.org/mailman/listinfo/async-sig
> Code of Conduct: https://www.python.org/psf/codeofconduct/


More information about the Async-sig mailing list