Skip to content

Commit

Permalink
Add support for prsetpdeathsig config option on Linux only.
Browse files Browse the repository at this point in the history
* Original pull inspired by: Supervisor#199
* If the supervisor process dies, that the child process will receive this signal set in prsetpdeathsig.
  • Loading branch information
lukeweber committed Sep 30, 2015
1 parent e5ae4d3 commit 40d1c92
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 6 deletions.
14 changes: 12 additions & 2 deletions supervisor/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -906,6 +906,15 @@ def get(section, opt, *args, **kwargs):
process_name = process_or_group_name(
get(section, 'process_name', '%(program_name)s', do_expand=False))

prsetpdeathsig = get(section, 'prsetpdeathsig', None)
if prsetpdeathsig is not None:
if sys.platform.startswith("linux"):
prsetpdeathsig = signal_number(prsetpdeathsig)
else:
raise ValueError(
"Cannot set prsetpdeathsig on non-linux os"
)

if numprocs > 1:
if not '%(process_num)' in process_name:
# process_name needs to include process_num when we
Expand Down Expand Up @@ -1002,7 +1011,8 @@ def get(section, opt, *args, **kwargs):
exitcodes=exitcodes,
redirect_stderr=redirect_stderr,
environment=environment,
serverurl=serverurl)
serverurl=serverurl,
prsetpdeathsig=prsetpdeathsig)

programs.append(pconfig)

Expand Down Expand Up @@ -1797,7 +1807,7 @@ class ProcessConfig(Config):
'stderr_logfile_backups', 'stderr_logfile_maxbytes',
'stderr_events_enabled', 'stderr_syslog',
'stopsignal', 'stopwaitsecs', 'stopasgroup', 'killasgroup',
'exitcodes', 'redirect_stderr' ]
'exitcodes', 'redirect_stderr', 'prsetpdeathsig' ]
optional_param_names = [ 'environment', 'serverurl' ]

def __init__(self, options, **params):
Expand Down
16 changes: 16 additions & 0 deletions supervisor/process.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import sys
import os
import time
import errno
Expand Down Expand Up @@ -296,6 +297,21 @@ def _spawn_as_child(self, filename, argv):
# supervisord from being sent to children.
options.setpgrp()

# Send this process a kill signal if supervisor crashes.
# Uses system call prctl(PR_SET_PDEATHSIG, <signal>).
# This will only work on Linux.
if self.config.prsetpdeathsig is not None \
and sys.platform.startswith("linux"):
# Constant from http://linux.die.net/include/linux/prctl.h
PR_SET_PDEATHSIG = 1
try:
import ctypes
import ctypes.util
libc = ctypes.cdll.LoadLibrary(ctypes.util.find_library('c'))
libc.prctl(PR_SET_PDEATHSIG, self.config.prsetpdeathsig)
except Exception:
options.logger.debug("Could not set parent death signal.")

self._prepare_child_fds()
# sending to fd 2 will put this output in the stderr log

Expand Down
3 changes: 2 additions & 1 deletion supervisor/tests/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,7 @@ def __init__(self, options, name, command, directory=None, umask=None,
stderr_logfile_backups=0, stderr_logfile_maxbytes=0,
redirect_stderr=False,
stopsignal=None, stopwaitsecs=10, stopasgroup=False, killasgroup=False,
exitcodes=(0,2), environment=None, serverurl=None):
exitcodes=(0,2), environment=None, serverurl=None, prsetpdeathsig=None):
self.options = options
self.name = name
self.command = command
Expand Down Expand Up @@ -550,6 +550,7 @@ def __init__(self, options, name, command, directory=None, umask=None,
self.umask = umask
self.autochildlogs_created = False
self.serverurl = serverurl
self.prsetpdeathsig = prsetpdeathsig

def create_autochildlogs(self):
self.autochildlogs_created = True
Expand Down
68 changes: 65 additions & 3 deletions supervisor/tests/test_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -1423,6 +1423,68 @@ def test_processes_from_section_host_node_name_expansion(self):
expected = "/bin/foo --host=" + platform.node()
self.assertEqual(pconfigs[0].command, expected)

def test_processes_from_section_prsetdeathsig_error(self):
instance = self._makeOne()
text = lstrip("""\
[program:foo]
command = /bin/foo
prsetpdeathsig = SIGKILL
""")
from supervisor.options import UnhosedConfigParser
config = UnhosedConfigParser()
config.read_string(text)

platform_mock = Mock()
platform_mock.return_value = "darwin"
@patch('sys.platform', platform_mock)
def parse_config(instance, config):
instance.processes_from_section(config, 'program:foo', 'bar')

try:
parse_config(instance, config)
except ValueError as exc:
self.assertTrue(exc.args[0].startswith(
'Cannot set prsetpdeathsig on non-linux os in section'))

def test_processes_from_section_prsetdeathsig_linux(self):
instance = self._makeOne()
text = lstrip("""\
[program:foo]
command = /bin/foo
prsetpdeathsig = SIGKILL
""")
from supervisor.options import UnhosedConfigParser
config = UnhosedConfigParser()
config.read_string(text)

platform_mock = Mock()
platform_mock.return_value = "linux"
@patch('sys.platform', platform_mock)
def parse_config(instance, config):
return instance.processes_from_section(config, 'program:foo', 'bar')

pconfig = parse_config(instance, config)
self.assertEqual(pconfig[0].prsetpdeathsig, signal.SIGKILL)

def test_processes_from_section_prsetdeathsig_linux_default(self):
instance = self._makeOne()
text = lstrip("""\
[program:foo]
command = /bin/foo
""")
from supervisor.options import UnhosedConfigParser
config = UnhosedConfigParser()
config.read_string(text)

platform_mock = Mock()
platform_mock.return_value = "linux"
@patch('sys.platform', platform_mock)
def parse_config(instance, config):
return instance.processes_from_section(config, 'program:foo', 'bar')

pconfig = parse_config(instance, config)
self.assertEqual(pconfig[0].prsetpdeathsig, None)

def test_processes_from_section_process_num_expansion(self):
instance = self._makeOne()
text = lstrip("""\
Expand Down Expand Up @@ -2677,7 +2739,7 @@ def _makeOne(self, *arg, **kw):
'stderr_events_enabled', 'stderr_syslog',
'stopsignal', 'stopwaitsecs', 'stopasgroup',
'killasgroup', 'exitcodes', 'redirect_stderr',
'environment'):
'environment', 'prsetpdeathsig'):
defaults[name] = name
for name in ('stdout_logfile_backups', 'stdout_logfile_maxbytes',
'stderr_logfile_backups', 'stderr_logfile_maxbytes'):
Expand Down Expand Up @@ -2759,7 +2821,7 @@ def _makeOne(self, *arg, **kw):
'stderr_events_enabled', 'stderr_syslog',
'stopsignal', 'stopwaitsecs', 'stopasgroup',
'killasgroup', 'exitcodes', 'redirect_stderr',
'environment'):
'environment', 'prsetpdeathsig'):
defaults[name] = name
for name in ('stdout_logfile_backups', 'stdout_logfile_maxbytes',
'stderr_logfile_backups', 'stderr_logfile_maxbytes'):
Expand Down Expand Up @@ -2807,7 +2869,7 @@ def _makeOne(self, *arg, **kw):
'stderr_events_enabled', 'stderr_syslog',
'stopsignal', 'stopwaitsecs', 'stopasgroup',
'killasgroup', 'exitcodes', 'redirect_stderr',
'environment'):
'environment', 'prsetpdeathsig'):
defaults[name] = name
for name in ('stdout_logfile_backups', 'stdout_logfile_maxbytes',
'stderr_logfile_backups', 'stderr_logfile_maxbytes'):
Expand Down
1 change: 1 addition & 0 deletions supervisor/tests/test_supervisord.py
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@ def make_pconfig(name, command, **params):
'stopasgroup': False,
'killasgroup': False,
'exitcodes': (0,2), 'environment': None, 'serverurl': None,
'prsetpdeathsig': None
}
result.update(params)
return ProcessConfig(options, **result)
Expand Down

0 comments on commit 40d1c92

Please sign in to comment.