瀏覽代碼

Fix handling of TERM signal to exit borgmatic, not just forward the signal to Borg (#516).

Dan Helfman 3 年之前
父節點
當前提交
0c027a3050
共有 3 個文件被更改,包括 56 次插入4 次删除
  1. 1 0
      NEWS
  2. 15 4
      borgmatic/signals.py
  3. 40 0
      tests/unit/test_signals.py

+ 1 - 0
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.
 
 

+ 15 - 4
borgmatic/signals.py

@@ -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 - 0
tests/unit/test_signals.py

@@ -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()