it-swarm.com.de

Wie kann das SIGTERM-Signal ordnungsgemäß verarbeitet werden?

Nehmen wir an, wir haben einen so trivialen Daemon in Python geschrieben:

def mainloop():
    while True:
        # 1. do
        # 2. some
        # 3. important
        # 4. job
        # 5. sleep

mainloop()

und daemonisieren wir es mit start-stop-daemon, das standardmäßig SIGTERM (TERM) Signal an --stop sendet.

Nehmen wir an, der aktuell ausgeführte Schritt ist #2. Und in diesem Moment senden wir ein TERM Signal.

Was passiert ist, dass die Ausführung sofort beendet wird.

Ich habe herausgefunden, dass ich das Signalereignis mit signal.signal(signal.SIGTERM, handler) verarbeiten kann, aber die aktuelle Ausführung wird immer noch unterbrochen und das Steuerelement an handler übergeben.

Meine Frage ist also: Ist es möglich, die aktuelle Ausführung nicht zu unterbrechen, sondern das TERM-Signal in einem getrennten Thread (?) Zu behandeln, sodass ich shutdown_flag = True so einstellen konnte, dass mainloop() die Möglichkeit hatte, elegant anzuhalten?

141
zerkms

Eine klassenbasierte, sauber zu verwendende Lösung:

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 __== '__main__':
  killer = GracefulKiller()
  while True:
    time.sleep(1)
    print("doing something in a loop ...")
    if killer.kill_now:
      break

  print "End of the program. I was killed gracefully :)"
199
Mayank Jaiswal

Erstens bin ich nicht sicher, dass Sie einen zweiten Thread benötigen, um die shutdown_flag zu setzen. Warum setzen Sie es nicht direkt in den SIGTERM-Handler?

Eine Alternative besteht darin, eine Ausnahme vom SIGTERM-Handler auszulösen, die auf den Stack übertragen wird. Angenommen, Sie verfügen über die richtige Ausnahmebehandlung (z. B. mit with/contextmanager- und try: ... finally:-Blöcken). Dies sollte ein ziemlich ordentliches Herunterfahren sein, ähnlich wie bei einem Ctrl-C-Programm.

Beispielprogramm 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"

Nun sehen Sie das Verhalten von Strg-C:

$ ./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

Diesmal sende ich SIGTERM nach 4 Iterationen mit 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

Diesmal aktiviere ich meinen benutzerdefinierten SIGTERM-Handler und sende ihn SIGTERM:

$ ./signals-test.py handle_signal
Hello
Iteration #1
Iteration #2
Iteration #3
Iteration #4
Goodbye
$ echo $?
0
39
Will Manley

Ich denke, Sie sind nahe an einer möglichen Lösung.

Führen Sie mainloop in einem separaten Thread aus und erweitern Sie sie mit der Eigenschaft shutdown_flag. Das Signal kann mit signal.signal(signal.SIGTERM, handler) im Haupt-Thread (nicht in einem separaten Thread) abgefangen werden. Der Signalhandler sollte shutdown_flag auf True setzen und warten, bis der Thread mit thread.join() endet.

27
moliware

Hier ist ein einfaches Beispiel ohne Threads oder Klassen.

import signal

run = True

def handler_stop_signals(signum, frame):
    global run
    run = False

signal.signal(signal.SIGINT, handler_stop_signals)
signal.signal(signal.SIGTERM, handler_stop_signals)

while run:
    pass # do stuff including other IO stuff
19
thoughtarray

Basierend auf den vorherigen Antworten habe ich einen Kontextmanager erstellt, der vor Sigt und Sigterm schützt.

import logging
import signal
import sys


class TerminateProtected:
    """ Protect a piece of code from being killed by SIGINT or SIGTERM.
    It can still be killed by a force kill.

    Example:
        with TerminateProtected():
            run_func_1()
            run_func_2()

    Both functions will be executed even if a sigterm or sigkill has been received.
    """
    killed = False

    def _handler(self, signum, frame):
        logging.error("Received SIGINT or SIGTERM! Finishing this block, then exiting.")
        self.killed = True

    def __enter__(self):
        self.old_sigint = signal.signal(signal.SIGINT, self._handler)
        self.old_sigterm = signal.signal(signal.SIGTERM, self._handler)

    def __exit__(self, type, value, traceback):
        if self.killed:
            sys.exit(0)
        signal.signal(signal.SIGINT, self.old_sigint)
        signal.signal(signal.SIGTERM, self.old_sigterm)


if __== '__main__':
    print("Try pressing ctrl+c while the sleep is running!")
    from time import sleep
    with TerminateProtected():
        sleep(10)
        print("Finished anyway!")
    print("This only prints if there was no sigint or sigterm")
10
Okke

Am einfachsten gefunden für mich ... Hier ein Beispiel mit Gabel für Klarheit, dass dieser Weg für die Flusskontrolle nützlich ist.

import signal
import time
import sys
import os

def handle_exit(sig, frame):
    raise(SystemExit)

def main():
    time.sleep(120)

signal.signal(signal.SIGTERM, handle_exit)

p = os.fork()
if p == 0:
    main()
    os._exit()

try:
    os.waitpid(p, 0)
except (KeyboardInterrupt, SystemExit):
    print('exit handled')
    os.kill(p, 15)
    os.waitpid(p, 0)
0
Kron