SIGTERM sinyali zarif bir şekilde nasıl işlenir?


198

Diyelim ki python ile yazılmış böyle önemsiz bir cini var:

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

mainloop()

ve bunu start-stop-daemonvarsayılan olarak send SIGTERM( TERM) sinyalini kullanarak daemonize ediyoruz --stop.

Farzedelim ki şu anki adımdır #2. Ve şu anda TERMsinyal gönderiyoruz .

Olan şey, infazın derhal sona ermesidir.

Sinyal olayını kullanarak işleyebileceğimi fark ettim signal.signal(signal.SIGTERM, handler)ama şey şu an mevcut yürütmeyi kesintiye uğratması ve kontrolü geçmesi handler.

Yani, sorum şu - geçerli yürütmeyi kesmek değil, ancak TERMayrı bir iş parçacığında (?) Sinyal işlemek mümkün shutdown_flag = Trueki böylece mainloop()zarif bir şekilde durdurmak için bir şans vardı ayarlamak mümkün mü?


2
Daha önce istediğini yaptım ve prosese signalfdteslimini maskeleyerek SIGTERM.
Eric Urban

Yanıtlar:


280

Sınıf tabanlı, temiz kullanımlı bir çözüm:

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 :)")

1
Fikir için teşekkürler! Yeniden başlatma korumasında değiştirilmiş bir yaklaşım kullandım. github.com/ryran/reboot-guard/blob/master/rguard#L284:L304
testere

7
Bu en iyi yanıttır (iş parçacığı gerekmez) ve tercih edilen ilk deneme yaklaşımı olmalıdır.
jose.angel.jimenez

2
@ Mausy5043 Python sınıfları tanımlamak için parantez olmamasına izin verir. Her ne kadar python 3.x için mükemmel olsa da, python 2.x için en iyi uygulama "class XYZ (object):" kullanmaktır. Sebep: docs.python.org/2/reference/datamodel.html#newstyle
Mayank Jaiswal

2
Sizi motive etmek için takip edin, teşekkürler. Bunu her zaman kullanıyorum.
chrisfauerbach

2
Daha kötü bir durumda, bu sadece incelikle kapanmadan önce başka bir yineleme yapmak anlamına gelir. FalseDeğeri sadece bir kere ayarlanır ve çoklu erişim sorunu değil bu yüzden o zaman sadece yanlıştan doğruya gidebilir.
Alceste_

52

İlk olarak, ayarlamak için ikinci bir konuya ihtiyacınız olduğundan emin değilim shutdown_flag.
Neden doğrudan SIGTERM işleyicisine ayarlamıyorsunuz?

Alternatif olarak SIGTERM, yığının üzerine yayılacak olan işleyiciden bir istisna oluşturmaktır. Uygun istisna işlemeye sahip olduğunuzu varsayarsak (örn. with/ contextmanagerVe try: ... finally:bloklarla) Ctrl+C, programınızda bulunmanıza benzeyen oldukça zarif bir kapatma olmalıdır .

Örnek program 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"

Şimdi Ctrl+Cdavranışa bakın :

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

Bu kez SIGTERM4 tekrardan sonra gönderirim 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

Bu kez özel SIGTERMişleyicimi etkinleştirip gönderiyorum SIGTERM:

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

3
"Neden doğrudan SIGTERM işleyicisine ayarlamıyorsunuz" --- çünkü işçi iş parçacığı rastgele bir yerde kesintiye uğrayacaktır. İşçi döngünüze birden fazla ifade koyarsanız, çözümünüzün bir işçiyi rastgele bir konumda sonlandırdığını görürsünüz, bu da işi bilinmeyen bir durumda bırakır.
zerkms

Docker bağlamında da benim için iyi çalışıyor. Teşekkürler!
Marian

4
Sadece bir bayrak ayarlarsanız ve istisna oluşturmazsanız, iş parçacığıyla aynı olacaktır. Yani iplik kullanmak burada gereksizdir.
Suor

28

Bence olası bir çözüme yakınsınız.

mainloopAyrı bir iş parçacığında yürüt ve özelliği ile genişlet shutdown_flag. Sinyal signal.signal(signal.SIGTERM, handler)ana dişe takılabilir (ayrı bir dişe değil). Sinyal işleyici shutdown_flagTrue olarak ayarlanmalı ve iş parçacığının bitmesini beklemelidir.thread.join()


4
Evet, ayrı bir iş parçacığı nihayet çözdüğüm, teşekkürler
zerkms

7
İş parçacığı burada gerekli değildir. Tek iş parçacıklı bir programda, önce bir sinyal işleyiciyi kaydedebilirsiniz (bir sinyal işleyicisini kaydetmek engellemez) ve sonra analoop yazabilirsiniz. Sinyal işleyici işlevi ne zaman bir bayrak ayarlamalı ve döngü bu bayrağı kontrol etmelidir. Burada da buna dayalı sınıf tabanlı bir çözüm yapıştırdım .
Mayank Jaiswal

2
İkinci bir ipliğe sahip olmanın hiçbir yolu yoktur. Sinyal işleyiciyi kaydedin.
oneloop


26

İşte iş parçacıkları veya sınıfları olmayan basit bir örnek.

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

11

Önceki cevaplara dayanarak, sigint ve sigterm'den koruyan bir bağlam yöneticisi oluşturdum.

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 __name__ == '__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")

4

Benim için en kolay yolu buldum. Burada bu şekilde akış kontrolü için yararlı olduğu netliği için çatallı bir örnek.

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

Yukarıdaki yanıtlardan ilham alarak bulduğum en basit çözüm

class SignalHandler:

    def __init__(self):

        # register signal handlers
        signal.signal(signal.SIGINT, self.exit_gracefully)
        signal.signal(signal.SIGTERM, self.exit_gracefully)

        self.logger = Logger(level=ERROR)

    def exit_gracefully(self, signum, frame):
        self.logger.info('captured signal %d' % signum)
        traceback.print_stack(frame)

        ###### do your resources clean up here! ####

        raise(SystemExit)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.