파이썬으로 작성된 사소한 데몬이 있다고 가정 해 봅시다.
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 = True
수 mainloop()
있었습니까?
답변
사용하기 편리한 클래스 기반 솔루션 :
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
/ contextmanager
및try: ... 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
이번에 SIGTERM
는 kill $(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_flag
True로 설정 하고 스레드가 끝나기를 기다립니다.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)
