[Python-ideas] atexit.register_w_signals()

Andrew Barnert abarnert at yahoo.com
Fri Feb 12 15:19:05 EST 2016


On Feb 12, 2016, at 10:36, Giampaolo Rodola' <g.rodola at gmail.com> wrote:
> So, one problem with atexit.register() is that it won't run the registered function in case the process is killed by a signal.

I think atexit is meant to be similar to the C feature of the same name. C atexit handlers are explicitly called only on "normal termination"--that is, returning from main(), or calling exit(). The reason C has other termination mechanisms like _exit() is to give you a way to skip the atexit handlers.

And POSIX defines signal handling in terms of _exit. All of the signals whose default action is T or A terminate the process "with all the consequences of _exit()"--meaning, specifically, that atexit handlers don't get called.

Maybe for some reason that design doesn't work as well for Python as it does for C. But if so, (a) I think you need to explain why, and (b) I think the replacement should look a lot less like the C mechanism, so it doesn't mislead people.

But really, Python already has a higher-level wrapper. Put a try/except, try/finally, with Exit Stack, plain with, etc. around your main function. That way, you can decide whether to handle only normal exits, normal exits plus Exception, normal edits plus BaseException, etc. And you can transform signals into exceptions (the way Python already does for SIGINT, and the way the example in the docs does for SIGALRM).

> import atexit, os, signal
> @atexit.register
> def cleanup():
>     print("on exit")  # XXX this never gets printed
> os.kill(os.getpid(), signal.SIGTERM)
> 
> The correct way to do that is to use signal.signal(). The problem with that though is that in case a third-party module has 
> already registered a function for that signal, the new function will overwrite the old one:

There's a solution for that: chaining signal handlers. When you call signal.signal to register your handler, you get the old handler back as a result. You can store that and have your handler call the old one, unless it's one of the special ones.

Or course that doesn't help when one library wants to ignore a signal with SIG_IGN and another wants to process it. Or when a library wants to dynamically register and unregister handlers after startup. It doesn't even work in the simple case unless everyone plays nicely--one library that doesn't do chaining breaks all the libraries that do.

Maybe a library to wrap up all those complexities so multiple libraries could cooperate better in signal handling would be useful?

> Also, we would still have to use atexit.register() so that the new function is called also on "clean" interpreter exit

Or, more simply, do the same thing people do in C: install a signal handler that calls exit() instead of doing explicit cleanup. Then you only have to register your cleanup in one place (whether that's atexit handlers, or something more Pythonic like a with or finally that gets triggered as the SystemExit passes along the exception handler chain).


More information about the Python-ideas mailing list