Fix handling of TERM signal to exit borgmatic, not just forward the signal to Borg (#516).
This commit is contained in:
parent
9f44bbad65
commit
0c027a3050
3 changed files with 56 additions and 4 deletions
1
NEWS
1
NEWS
|
@ -1,4 +1,5 @@
|
||||||
1.5.25.dev0
|
1.5.25.dev0
|
||||||
|
* #516: Fix handling of TERM signal to exit borgmatic, not just forward the signal to Borg.
|
||||||
* #517: Fix borgmatic exit code (so it's zero) when initial Borg calls fail but later retries
|
* #517: Fix borgmatic exit code (so it's zero) when initial Borg calls fail but later retries
|
||||||
succeed.
|
succeed.
|
||||||
|
|
||||||
|
|
|
@ -1,23 +1,34 @@
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import signal
|
import signal
|
||||||
|
import sys
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def _handle_signal(signal_number, frame): # pragma: no cover
|
EXIT_CODE_FROM_SIGNAL = 128
|
||||||
|
|
||||||
|
|
||||||
|
def handle_signal(signal_number, frame):
|
||||||
'''
|
'''
|
||||||
Send the signal to all processes in borgmatic's process group, which includes child processes.
|
Send the signal to all processes in borgmatic's process group, which includes child processes.
|
||||||
'''
|
'''
|
||||||
# Prevent infinite signal handler recursion. If the parent frame is this very same handler
|
# Prevent infinite signal handler recursion. If the parent frame is this very same handler
|
||||||
# function, we know we're recursing.
|
# function, we know we're recursing.
|
||||||
if frame.f_back.f_code.co_name == _handle_signal.__name__:
|
if frame.f_back.f_code.co_name == handle_signal.__name__:
|
||||||
return
|
return
|
||||||
|
|
||||||
os.killpg(os.getpgrp(), signal_number)
|
os.killpg(os.getpgrp(), signal_number)
|
||||||
|
|
||||||
|
if signal_number == signal.SIGTERM:
|
||||||
|
logger.critical('Exiting due to TERM signal')
|
||||||
|
sys.exit(EXIT_CODE_FROM_SIGNAL + signal.SIGTERM)
|
||||||
|
|
||||||
def configure_signals(): # pragma: no cover
|
|
||||||
|
def configure_signals():
|
||||||
'''
|
'''
|
||||||
Configure borgmatic's signal handlers to pass relevant signals through to any child processes
|
Configure borgmatic's signal handlers to pass relevant signals through to any child processes
|
||||||
like Borg. Note that SIGINT gets passed through even without these changes.
|
like Borg. Note that SIGINT gets passed through even without these changes.
|
||||||
'''
|
'''
|
||||||
for signal_number in (signal.SIGHUP, signal.SIGTERM, signal.SIGUSR1, signal.SIGUSR2):
|
for signal_number in (signal.SIGHUP, signal.SIGTERM, signal.SIGUSR1, signal.SIGUSR2):
|
||||||
signal.signal(signal_number, _handle_signal)
|
signal.signal(signal_number, handle_signal)
|
||||||
|
|
40
tests/unit/test_signals.py
Normal file
40
tests/unit/test_signals.py
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
from flexmock import flexmock
|
||||||
|
|
||||||
|
from borgmatic import signals as module
|
||||||
|
|
||||||
|
|
||||||
|
def test_handle_signal_forwards_to_subprocesses():
|
||||||
|
signal_number = 100
|
||||||
|
frame = flexmock(f_back=flexmock(f_code=flexmock(co_name='something')))
|
||||||
|
process_group = flexmock()
|
||||||
|
flexmock(module.os).should_receive('getpgrp').and_return(process_group)
|
||||||
|
flexmock(module.os).should_receive('killpg').with_args(process_group, signal_number).once()
|
||||||
|
|
||||||
|
module.handle_signal(signal_number, frame)
|
||||||
|
|
||||||
|
|
||||||
|
def test_handle_signal_bails_on_recursion():
|
||||||
|
signal_number = 100
|
||||||
|
frame = flexmock(f_back=flexmock(f_code=flexmock(co_name='handle_signal')))
|
||||||
|
flexmock(module.os).should_receive('getpgrp').never()
|
||||||
|
flexmock(module.os).should_receive('killpg').never()
|
||||||
|
|
||||||
|
module.handle_signal(signal_number, frame)
|
||||||
|
|
||||||
|
|
||||||
|
def test_handle_signal_exits_on_sigterm():
|
||||||
|
signal_number = module.signal.SIGTERM
|
||||||
|
frame = flexmock(f_back=flexmock(f_code=flexmock(co_name='something')))
|
||||||
|
flexmock(module.os).should_receive('getpgrp').and_return(flexmock)
|
||||||
|
flexmock(module.os).should_receive('killpg')
|
||||||
|
flexmock(module.sys).should_receive('exit').with_args(
|
||||||
|
module.EXIT_CODE_FROM_SIGNAL + signal_number
|
||||||
|
).once()
|
||||||
|
|
||||||
|
module.handle_signal(signal_number, frame)
|
||||||
|
|
||||||
|
|
||||||
|
def test_configure_signals_installs_signal_handlers():
|
||||||
|
flexmock(module.signal).should_receive('signal').at_least().once()
|
||||||
|
|
||||||
|
module.configure_signals()
|
Loading…
Reference in a new issue