Package: pyterminate
pyterminateReliably run cleanup code upon program termination.Table of ContentsWhy does this exist?What can it do?QuickstartTips, tricks, and other notesDuplicate registration after forkingMultiprocessing start methodWhy does this exist?There are currently two builtin modules for handling termination behavior
in Python:atexitandsignal. However, using them
directly leads to a lot of repeated boilerplate code, and some non-obvious
behaviors that can be easy to accidentally get wrong, which is why I wrote this
package.Theatexitmodule is currently insufficient since it fails to handle signals.
Thesignalmodule is currently insufficient since it fails to handle normal
or exception-caused exits.Typical approaches would include frequently repeated code registering a
function both withatexitand on desired signals. However, extra care
sometimes needs to be taken to ensure the function doesn't run twice (or is
idempotent), and that a previously registered signal handler gets called.What can it do?This packages does or allows the following behavior:Register a function to be called on program terminationAlways on normal or exception-caused termination:@pyterminate.registerConfigurable for any desired signals:@pyterminate.register(signals=(signal.SIGINT, signal.SIGABRT))Allows multiple functions to be registeredWill call previous registered signal handlersAllows zero or non-zero exit codes on captured signals:@pyterminate.register(successful_exit=True)Allows suppressing or throwing ofKeyboardInterruptonSIGINT:@pyterminate.register(keyboard_interrupt_on_sigint=True)You may want to throw aKeyboardInterruptif there is additional
exception handling defined.Allows functions to be unregistered:pyterminate.unregister(func)Ignore requested signals while registered function is executing, ensuring
that it is not interrupted.It's important to note thatSIGKILLand calls toos._exit()cannot be
ignored.Quickstartpython3-mpipinstallpyterminateimportsignalimportpyterminate@pyterminate.register(args=(None,),kwargs={"b":42},signals=(signal.SIGINT,signal.SIGTERM),successful_exit=True,keyboard_interrupt_on_sigint=True)defcleanup(*args,**kwargs):...# orpyterminate.register(cleanup,...)Tips, tricks, and other notesDuplicate registration after forkingSince creating a new process through forking duplicates the entire process,
any previously registered functions will also be registered in the forked
process. This is an obvious consequence of forking, but important to
consider if the registered functions are accessing shared resources. To
avoid this behavior, you can unregister the function at the beginning of
the forked process, gate based on the process' ID, or use any other
synchronization method that's appropriate.Multiprocessing start methodWhen starting processes with Python'smultiprocessingmodule, theforkmethod will fail to call registered functions on exit, since
the process is ended withos._exit()internally, which bypasses all cleanup
and immediately kills the process.One way of getting around this are using the"spawn"start method if that
is acceptable for your application. Another method is to register your function
to a user-defined signal, and wrap your process code in try-except block,
raising the user-defined signal at the end.pyterminateprovides this
functionality in the form of theexit_with_signaldecorator, which simply
wraps the decorated function in a try-finally block, and raises the given
signal. Example usage:importmultiprocessingasmpimportsignalimportpyterminate@pyterminate.exit_with_signal(signal.SIGUSR1)defrun_process():@pyterminate.register(signals=[signal.SIGUSR1,signal.SIGINT,signal.SIGTERM])defcleanup():......if__name__=="__main__"mp.set_start_method("fork")proc=mp.Process(target=run_process)proc.start()try:proc.join(timeout=300)exceptTimeoutError:proc.terminate()proc.join()