Stay alive on double SIGHUP
There's a short window where sending a second SIGHUP will cause the process to exit rather than reload config. This causes some confusion to operators/testers and is hazardous for use with a config management system. It would be easy to write plays that accidentally, even intermittently, cause the service to exit. Disallow this for SIGHUP. Graceful/fast shutdowns can still be achieved with SIGTERM/SIGINT. Change-Id: Ia2146b25b857217d498bf785c17bd0b629174c86
This commit is contained in:
parent
0c3a29d139
commit
114041b18e
@ -289,6 +289,7 @@ class ServiceLauncher(Launcher):
|
||||
|
||||
def handle_signal(self):
|
||||
"""Set self._handle_signal as a signal handler."""
|
||||
self.signal_handler.clear()
|
||||
self.signal_handler.add_handler('SIGTERM', self._graceful_shutdown)
|
||||
self.signal_handler.add_handler('SIGINT', self._fast_exit)
|
||||
self.signal_handler.add_handler('SIGHUP', self._reload_service)
|
||||
@ -372,13 +373,13 @@ class ProcessLauncher(object):
|
||||
|
||||
def handle_signal(self):
|
||||
"""Add instance's signal handlers to class handlers."""
|
||||
self.signal_handler.add_handlers(('SIGTERM', 'SIGHUP'),
|
||||
self._handle_signal)
|
||||
self.signal_handler.add_handler('SIGTERM', self._handle_term)
|
||||
self.signal_handler.add_handler('SIGHUP', self._handle_hup)
|
||||
self.signal_handler.add_handler('SIGINT', self._fast_exit)
|
||||
self.signal_handler.add_handler('SIGALRM', self._on_alarm_exit)
|
||||
|
||||
def _handle_signal(self, signo, frame):
|
||||
"""Set signal handlers.
|
||||
def _handle_term(self, signo, frame):
|
||||
"""Handle a TERM event.
|
||||
|
||||
:param signo: signal number
|
||||
:param frame: current stack frame
|
||||
@ -389,6 +390,19 @@ class ProcessLauncher(object):
|
||||
# Allow the process to be killed again and die from natural causes
|
||||
self.signal_handler.clear()
|
||||
|
||||
def _handle_hup(self, signo, frame):
|
||||
"""Handle a HUP event.
|
||||
|
||||
:param signo: signal number
|
||||
:param frame: current stack frame
|
||||
"""
|
||||
self.sigcaught = signo
|
||||
self.running = False
|
||||
|
||||
# Do NOT clear the signal_handler, allowing multiple SIGHUPs to be
|
||||
# received swiftly. If a non-HUP is received before #wait loops, the
|
||||
# second event will "overwrite" the HUP. This is fine.
|
||||
|
||||
def _fast_exit(self, signo, frame):
|
||||
LOG.info(_LI('Caught SIGINT signal, instantaneous exiting'))
|
||||
os._exit(1)
|
||||
|
@ -535,6 +535,38 @@ class ProcessLauncherTest(base.ServiceBaseTestCase):
|
||||
serv = FooService()
|
||||
self.assertRaises(TypeError, launcher.launch_service, serv, 0)
|
||||
|
||||
@mock.patch("oslo_service.service.ProcessLauncher._start_child")
|
||||
@mock.patch("oslo_service.service.ProcessLauncher.handle_signal")
|
||||
@mock.patch("eventlet.greenio.GreenPipe")
|
||||
@mock.patch("os.pipe")
|
||||
def test_double_sighup(self, pipe_mock, green_pipe_mock,
|
||||
handle_signal_mock, start_child_mock):
|
||||
# Test that issuing two SIGHUPs in a row does not exit; then send a
|
||||
# TERM that does cause an exit.
|
||||
pipe_mock.return_value = [None, None]
|
||||
launcher = service.ProcessLauncher(self.conf)
|
||||
serv = _Service()
|
||||
launcher.launch_service(serv, workers=0)
|
||||
|
||||
def stager():
|
||||
# -1: start state
|
||||
# 0: post-init
|
||||
# 1: first HUP sent
|
||||
# 2: second HUP sent
|
||||
# 3: TERM sent
|
||||
stager.stage += 1
|
||||
if stager.stage < 3:
|
||||
launcher._handle_hup(1, mock.sentinel.frame)
|
||||
elif stager.stage == 3:
|
||||
launcher._handle_term(15, mock.sentinel.frame)
|
||||
else:
|
||||
self.fail("TERM did not kill launcher")
|
||||
stager.stage = -1
|
||||
handle_signal_mock.side_effect = stager
|
||||
|
||||
launcher.wait()
|
||||
self.assertEqual(3, stager.stage)
|
||||
|
||||
|
||||
class GracefulShutdownTestService(service.Service):
|
||||
def __init__(self):
|
||||
|
Loading…
x
Reference in New Issue
Block a user