¿Cómo procesar la señal SIGTERM con gracia?
Supongamos que tenemos un demonio tan trivial escrito en Python:
def mainloop():
while True:
# 1. do
# 2. some
# 3. important
# 4. job
# 5. sleep
mainloop()
y lo demonizamos usando start-stop-daemon
el cual por defecto envía la señal SIGTERM
( TERM
) en--stop
.
Supongamos que el paso actual realizado es #2
. Y en este mismo momento estamos enviandoTERM
señal.
Lo que sucede es que la ejecución termina inmediatamente.
Descubrí que puedo manejar el evento de señal usando signal.signal(signal.SIGTERM, handler)
, pero el problema es que aún interrumpe la ejecución actual y pasa el control ahandler
.
Entonces, mi pregunta es: ¿es posible no interrumpir la ejecución actual sino manejar la TERM
señal en un hilo separado (?) para poder configurarlo shutdown_flag = True
y mainloop()
tener la oportunidad de detenerlo correctamente?
Una solución limpia para usar basada en clases:
import signal
import time
class GracefulKiller:
kill_now = False
def __init__(self):
signal.signal(signal.SIGINT, self.exit_gracefully)
signal.signal(signal.SIGTERM, self.exit_gracefully)
def exit_gracefully(self, signum, frame):
self.kill_now = True
if __name__ == '__main__':
killer = GracefulKiller()
while not killer.kill_now:
time.sleep(1)
print("doing something in a loop ...")
print("End of the program. I was killed gracefully :)")
Primero, no estoy seguro de que necesites un segundo hilo para configurar el archivo shutdown_flag
.
¿Por qué no configurarlo directamente en el controlador SIGTERM?
Una alternativa es generar una excepción desde el SIGTERM
controlador, que se propagará hacia la pila. Suponiendo que tenga un manejo de excepciones adecuado (por ejemplo, con bloques with
/ contextmanager
y try: ... finally:
), este debería ser un cierre bastante elegante, similar a si estuviera en Ctrl+Csu programa.
Programa de ejemplo signals-test.py
:
#!/usr/bin/python
from time import sleep
import signal
import sys
def sigterm_handler(_signo, _stack_frame):
# Raises SystemExit(0):
sys.exit(0)
if sys.argv[1] == "handle_signal":
signal.signal(signal.SIGTERM, sigterm_handler)
try:
print "Hello"
i = 0
while True:
i += 1
print "Iteration #%i" % i
sleep(1)
finally:
print "Goodbye"
Ahora mira el Ctrl+Ccomportamiento:
$ ./signals-test.py default
Hello
Iteration #1
Iteration #2
Iteration #3
Iteration #4
^CGoodbye
Traceback (most recent call last):
File "./signals-test.py", line 21, in <module>
sleep(1)
KeyboardInterrupt
$ echo $?
1
Esta vez lo envío SIGTERM
después de 4 iteraciones con kill $(ps aux | grep signals-test | awk '/python/ {print $2}')
:
$ ./signals-test.py default
Hello
Iteration #1
Iteration #2
Iteration #3
Iteration #4
Terminated
$ echo $?
143
Esta vez habilito mi SIGTERM
controlador personalizado y lo envío SIGTERM
:
$ ./signals-test.py handle_signal
Hello
Iteration #1
Iteration #2
Iteration #3
Iteration #4
Goodbye
$ echo $?
0