24

I have a python process which runs in background, and I would like it to generate some output only when the script is terminated.

def handle_exit():
    print('\nAll files saved in ' + directory)
    generate_output()

atexit.register(handle_exit)

Calling raising a KeyboardInterupt exception and sys.exit() calls handle_exit() properly, but if I were to do kill {PID} from the terminal it terminates the script without calling handle_exit().

Is there a way to terminate the process that is running in the background, and still have it run handle_exit() before terminating?

Chan Jing Hong
  • 2,251
  • 4
  • 22
  • 41
  • 1
    Not possible with atexit alone. As the documentation states `The functions registered via this module are not called when the program is killed by a signal not handled by Python, when a Python fatal internal error is detected, or when os._exit() is called.` [here](https://docs.python.org/2/library/atexit.html) – Alexander Ejbekov Nov 29 '16 at 13:19

4 Answers4

36

Try signal.signal. It allows to catch any system signal:

import signal

def handle_exit():
    print('\nAll files saved in ' + directory)
    generate_output()

atexit.register(handle_exit)
signal.signal(signal.SIGTERM, handle_exit)
signal.signal(signal.SIGINT, handle_exit)

Now you can kill {pid} and handle_exit will be executed.

Gennady Kandaurov
  • 1,914
  • 1
  • 15
  • 19
  • 6
    N.B., your handler function signatures should afford two positional arguments – a signal number and a stack frame, e.g. `handle_exit(signum, frame)` or `handle_exit(*args)` – as that’s how those functions invoked via `signal.signal(…)` will be called. If your functions take explicit arguments, you can set up values for `atexit.register(…)` to pass on its own invocation, e.g. `atexit.register(handle_exit, None, None)` or similar. – fish2000 Nov 22 '19 at 01:09
  • 3
    Doesn't work on Windows when closing via Task Manager or via Stop button in PyCharm – IlyaP Jan 02 '21 at 09:34
  • This will execute `handle_exit` _twice_ - once from handle_exit and _then again_ from atexit.register. – KamilCuk Feb 13 '23 at 10:49
1

To enable signals when debugging PyCharm on Windows:

  1. Within PyCharm hit Ctrl + Shift + A to bring up the "Find Actions..." menu
  2. Search for "Registry" and hit enter
  3. Find the key kill.windows.processes.softly and enable it (you can start typing "kill" and it will search for the key)
  4. Restart PyCharm
ChaimG
  • 7,024
  • 4
  • 38
  • 46
0

To check your system and see which signal is being called:

import signal
import time


def handle_signal(sig_id, frame):
    sig = {x.value: x for x in signal.valid_signals()}.get(sig_id)
    print(f'{sig.name}, {sig_id=}, {frame=}')
    exit(-1)


for sig in signal.valid_signals():
    print(f'{sig.value}: signal.{sig.name},')
    signal.signal(sig, handle_signal)

time.sleep(30)
ChaimG
  • 7,024
  • 4
  • 38
  • 46
0

If you want to handle signals yourself, according to the Python documentation, the handler function needs two arguments.

If you handle the SIGINT signal, you must call sys.exit(0) within the handler function; otherwise, the program will not terminate upon pressing Ctrl + C.

As KamiCuk mentioned, if the function registered with atexit is the same as the signal handler function, the function will be called twice.

Therefore, I prefer using separate functions for the signal handler and for the atexit registration. Here is an example:

import atexit
import signal
import sys

def on_exit():
    print('real clean code here')

def handle_exit(signum, frame):
    sys.exit(0)

atexit.register(on_exit)
signal.signal(signal.SIGTERM, handle_exit)
signal.signal(signal.SIGINT, handle_exit)

Handling these signals correctly can be tricky, especially for cross-platform applications.

To address this, I have created a package called safe-exit.

The documentation can be found here: https://safe-exit.readthedocs.io/en/latest/

John Zhang
  • 73
  • 4