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


197

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

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()있었습니까?


2
프로세스 signalfd전달을 사용 하고 숨겨서 이전에 요청한 것을 SIGTERM수행했습니다.
Eric Urban

답변:


275

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

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
아이디어 주셔서 감사합니다! reboot-guard에서 수정 된 접근 방식을 사용했습니다. github.com/ryran/reboot-guard/blob/master/rguard#L284:L304
rsaw

7
이것은 가장 좋은 대답이며 스레드가 필요하지 않으며 선호하는 첫 번째 시도 방법이어야합니다.
jose.angel.jimenez

2
@ Mausy5043 Python을 사용하면 클래스를 정의하기위한 괄호를 사용할 수 없습니다. python 3.x에는 완벽하지만 python 2.x에는 완벽하지만 "class XYZ (object) :"를 사용하는 것이 가장 좋습니다. 이유 : docs.python.org/2/reference/datamodel.html#newstyle
Mayank Jaiswal

2
후속 조치를 취하고 동기를 부여하십시오. 감사합니다. 나는 이것을 항상 사용합니다.
chrisfauerbach

2
더 나쁜 경우, 이는 정상적으로 종료하기 전에 다른 반복을 수행하는 것을 의미합니다. 이 False값은 한 번만 설정 한 다음 False에서 True로만 이동할 수 있으므로 다중 액세스는 문제가되지 않습니다.
Alceste_

52

먼저,를 설정하기 위해 두 번째 스레드가 필요한지 확실하지 않습니다 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

3
"SIGTERM 핸들러에서 직접 설정하지 않는 이유"--- 작업자 스레드가 임의의 위치에서 인터럽트되기 때문입니다. 작업자 루프에 여러 명령문을 넣으면 솔루션이 임의의 위치에서 작업자를 종료하여 작업을 알 수없는 상태로 유지함을 알 수 있습니다.
zerkms

Docker 컨텍스트에서도 잘 작동합니다. 감사!
Marian

4
플래그를 설정하고 예외를 발생시키지 않으면 스레드와 동일합니다. 따라서 스레드 사용은 불필요한 것입니다.
Suor

28

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

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


4
그래, 분리 된 스레드는 내가 마침내 그것을 해결 한 방법입니다. 덕분에
zerkms

7
여기에는 스레드가 필요하지 않습니다. 단일 스레드 프로그램 자체에서 먼저 신호 핸들러를 등록한 후 (신호 핸들러를 등록하는 것은 비 블로킹 임) 메인 루프를 작성할 수 있습니다. 신호 처리기 함수는 플래그가 루프를 설정해야 할 때 플래그를 설정해야합니다. 나는 여기 에 같은 클래스 기반 솔루션을 붙여 넣었 습니다 .
Mayank Jaiswal

2
두 번째 실을 가질 필요는 없습니다. 신호 핸들러를 등록하십시오.
oneloop

유용한 페이지 : g-loaded.eu/2016/11/24/…
Kamil Sindi

26

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

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

이전 답변을 바탕으로 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")

4

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

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

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

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)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.