[python] SIGTERM 신호를 정상적으로 처리하는 방법?

파이썬으로 작성된 사소한 데몬이 있다고 가정 해 봅시다.

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

mainloop()

그리고 우리 start-stop-daemon는 기본적으로 SIGTERM( TERM) 신호를 보내는 데 사용하여 이를 초기화 합니다 --stop.

수행 된 현재 단계가이라고 가정 해 봅시다 #2. 그리고 바로 지금 우리는 TERM신호를 보내고 있습니다.

실행은 즉시 종료됩니다.

신호 이벤트를 사용하여 처리 할 수 signal.signal(signal.SIGTERM, handler)있지만 여전히 현재 실행을 중단하고 컨트롤을에 전달합니다 handler.

그래서 내 질문은-현재 실행을 중단하지 않고 TERM분리 된 스레드 (?)에서 신호를 처리하여 정상적으로 중지 할 수 있도록 설정할 shutdown_flag = Truemainloop()있었습니까?



답변

사용하기 편리한 클래스 기반 솔루션 :

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


답변

먼저,를 설정하기 위해 두 번째 스레드가 필요한지 확실하지 않습니다 shutdown_flag.
왜 SIGTERM 핸들러에서 직접 설정하지 않습니까?

대안은 SIGTERM처리기 에서 예외를 발생 시켜 스택으로 전파되는 것입니다. 적절한 예외 처리가 있다고 가정합니다 (예 : with/ contextmanagertry: ... finally: 블록)Ctrl+C 프로그램 을 사용하는 경우와 마찬가지로 상당히 정상적으로 종료 해야합니다.

프로그램 예 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"

이제 Ctrl+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

이번에 SIGTERMkill $(ps aux | grep signals-test | awk '/python/ {print $2}')다음 과 같이 4 번 반복하여 보냅니다 .

$ ./signals-test.py default
Hello
Iteration #1
Iteration #2
Iteration #3
Iteration #4
Terminated
$ echo $?
143

이번에는 사용자 정의 SIGTERM처리기를 활성화 하여 보냅니다 SIGTERM.

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


답변

나는 당신이 가능한 해결책에 가깝다고 생각합니다.

mainloop별도의 스레드에서 실행 하고 속성으로 확장하십시오 shutdown_flag. 신호는 signal.signal(signal.SIGTERM, handler)메인 스레드에서 잡을 수 있습니다 (별도의 스레드가 아님). 신호 처리기는 shutdown_flagTrue로 설정 하고 스레드가 끝나기를 기다립니다.thread.join()


답변

다음은 스레드 또는 클래스가없는 간단한 예입니다.

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


답변

이전 답변을 바탕으로 sigint 및 sigterm으로부터 보호하는 컨텍스트 관리자를 작성했습니다.

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


답변

가장 쉬운 방법을 찾았습니다. 이 방법이 흐름 제어에 유용하다는 것을 명확히하기 위해 포크가있는 예제입니다.

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)


답변

위의 응답으로 영감을 얻은 가장 간단한 해결책은 다음과 같습니다.

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)