Skip to content

Commit 40d1c92

Browse files
committed
Add support for prsetpdeathsig config option on Linux only.
* Original pull inspired by: Supervisor#199 * If the supervisor process dies, that the child process will receive this signal set in prsetpdeathsig.
1 parent e5ae4d3 commit 40d1c92

File tree

5 files changed

+96
-6
lines changed

5 files changed

+96
-6
lines changed

supervisor/options.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -906,6 +906,15 @@ def get(section, opt, *args, **kwargs):
906906
process_name = process_or_group_name(
907907
get(section, 'process_name', '%(program_name)s', do_expand=False))
908908

909+
prsetpdeathsig = get(section, 'prsetpdeathsig', None)
910+
if prsetpdeathsig is not None:
911+
if sys.platform.startswith("linux"):
912+
prsetpdeathsig = signal_number(prsetpdeathsig)
913+
else:
914+
raise ValueError(
915+
"Cannot set prsetpdeathsig on non-linux os"
916+
)
917+
909918
if numprocs > 1:
910919
if not '%(process_num)' in process_name:
911920
# process_name needs to include process_num when we
@@ -1002,7 +1011,8 @@ def get(section, opt, *args, **kwargs):
10021011
exitcodes=exitcodes,
10031012
redirect_stderr=redirect_stderr,
10041013
environment=environment,
1005-
serverurl=serverurl)
1014+
serverurl=serverurl,
1015+
prsetpdeathsig=prsetpdeathsig)
10061016

10071017
programs.append(pconfig)
10081018

@@ -1797,7 +1807,7 @@ class ProcessConfig(Config):
17971807
'stderr_logfile_backups', 'stderr_logfile_maxbytes',
17981808
'stderr_events_enabled', 'stderr_syslog',
17991809
'stopsignal', 'stopwaitsecs', 'stopasgroup', 'killasgroup',
1800-
'exitcodes', 'redirect_stderr' ]
1810+
'exitcodes', 'redirect_stderr', 'prsetpdeathsig' ]
18011811
optional_param_names = [ 'environment', 'serverurl' ]
18021812

18031813
def __init__(self, options, **params):

supervisor/process.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import sys
12
import os
23
import time
34
import errno
@@ -296,6 +297,21 @@ def _spawn_as_child(self, filename, argv):
296297
# supervisord from being sent to children.
297298
options.setpgrp()
298299

300+
# Send this process a kill signal if supervisor crashes.
301+
# Uses system call prctl(PR_SET_PDEATHSIG, <signal>).
302+
# This will only work on Linux.
303+
if self.config.prsetpdeathsig is not None \
304+
and sys.platform.startswith("linux"):
305+
# Constant from http://linux.die.net/include/linux/prctl.h
306+
PR_SET_PDEATHSIG = 1
307+
try:
308+
import ctypes
309+
import ctypes.util
310+
libc = ctypes.cdll.LoadLibrary(ctypes.util.find_library('c'))
311+
libc.prctl(PR_SET_PDEATHSIG, self.config.prsetpdeathsig)
312+
except Exception:
313+
options.logger.debug("Could not set parent death signal.")
314+
299315
self._prepare_child_fds()
300316
# sending to fd 2 will put this output in the stderr log
301317

supervisor/tests/base.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -516,7 +516,7 @@ def __init__(self, options, name, command, directory=None, umask=None,
516516
stderr_logfile_backups=0, stderr_logfile_maxbytes=0,
517517
redirect_stderr=False,
518518
stopsignal=None, stopwaitsecs=10, stopasgroup=False, killasgroup=False,
519-
exitcodes=(0,2), environment=None, serverurl=None):
519+
exitcodes=(0,2), environment=None, serverurl=None, prsetpdeathsig=None):
520520
self.options = options
521521
self.name = name
522522
self.command = command
@@ -550,6 +550,7 @@ def __init__(self, options, name, command, directory=None, umask=None,
550550
self.umask = umask
551551
self.autochildlogs_created = False
552552
self.serverurl = serverurl
553+
self.prsetpdeathsig = prsetpdeathsig
553554

554555
def create_autochildlogs(self):
555556
self.autochildlogs_created = True

supervisor/tests/test_options.py

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1423,6 +1423,68 @@ def test_processes_from_section_host_node_name_expansion(self):
14231423
expected = "/bin/foo --host=" + platform.node()
14241424
self.assertEqual(pconfigs[0].command, expected)
14251425

1426+
def test_processes_from_section_prsetdeathsig_error(self):
1427+
instance = self._makeOne()
1428+
text = lstrip("""\
1429+
[program:foo]
1430+
command = /bin/foo
1431+
prsetpdeathsig = SIGKILL
1432+
""")
1433+
from supervisor.options import UnhosedConfigParser
1434+
config = UnhosedConfigParser()
1435+
config.read_string(text)
1436+
1437+
platform_mock = Mock()
1438+
platform_mock.return_value = "darwin"
1439+
@patch('sys.platform', platform_mock)
1440+
def parse_config(instance, config):
1441+
instance.processes_from_section(config, 'program:foo', 'bar')
1442+
1443+
try:
1444+
parse_config(instance, config)
1445+
except ValueError as exc:
1446+
self.assertTrue(exc.args[0].startswith(
1447+
'Cannot set prsetpdeathsig on non-linux os in section'))
1448+
1449+
def test_processes_from_section_prsetdeathsig_linux(self):
1450+
instance = self._makeOne()
1451+
text = lstrip("""\
1452+
[program:foo]
1453+
command = /bin/foo
1454+
prsetpdeathsig = SIGKILL
1455+
""")
1456+
from supervisor.options import UnhosedConfigParser
1457+
config = UnhosedConfigParser()
1458+
config.read_string(text)
1459+
1460+
platform_mock = Mock()
1461+
platform_mock.return_value = "linux"
1462+
@patch('sys.platform', platform_mock)
1463+
def parse_config(instance, config):
1464+
return instance.processes_from_section(config, 'program:foo', 'bar')
1465+
1466+
pconfig = parse_config(instance, config)
1467+
self.assertEqual(pconfig[0].prsetpdeathsig, signal.SIGKILL)
1468+
1469+
def test_processes_from_section_prsetdeathsig_linux_default(self):
1470+
instance = self._makeOne()
1471+
text = lstrip("""\
1472+
[program:foo]
1473+
command = /bin/foo
1474+
""")
1475+
from supervisor.options import UnhosedConfigParser
1476+
config = UnhosedConfigParser()
1477+
config.read_string(text)
1478+
1479+
platform_mock = Mock()
1480+
platform_mock.return_value = "linux"
1481+
@patch('sys.platform', platform_mock)
1482+
def parse_config(instance, config):
1483+
return instance.processes_from_section(config, 'program:foo', 'bar')
1484+
1485+
pconfig = parse_config(instance, config)
1486+
self.assertEqual(pconfig[0].prsetpdeathsig, None)
1487+
14261488
def test_processes_from_section_process_num_expansion(self):
14271489
instance = self._makeOne()
14281490
text = lstrip("""\
@@ -2677,7 +2739,7 @@ def _makeOne(self, *arg, **kw):
26772739
'stderr_events_enabled', 'stderr_syslog',
26782740
'stopsignal', 'stopwaitsecs', 'stopasgroup',
26792741
'killasgroup', 'exitcodes', 'redirect_stderr',
2680-
'environment'):
2742+
'environment', 'prsetpdeathsig'):
26812743
defaults[name] = name
26822744
for name in ('stdout_logfile_backups', 'stdout_logfile_maxbytes',
26832745
'stderr_logfile_backups', 'stderr_logfile_maxbytes'):
@@ -2759,7 +2821,7 @@ def _makeOne(self, *arg, **kw):
27592821
'stderr_events_enabled', 'stderr_syslog',
27602822
'stopsignal', 'stopwaitsecs', 'stopasgroup',
27612823
'killasgroup', 'exitcodes', 'redirect_stderr',
2762-
'environment'):
2824+
'environment', 'prsetpdeathsig'):
27632825
defaults[name] = name
27642826
for name in ('stdout_logfile_backups', 'stdout_logfile_maxbytes',
27652827
'stderr_logfile_backups', 'stderr_logfile_maxbytes'):
@@ -2807,7 +2869,7 @@ def _makeOne(self, *arg, **kw):
28072869
'stderr_events_enabled', 'stderr_syslog',
28082870
'stopsignal', 'stopwaitsecs', 'stopasgroup',
28092871
'killasgroup', 'exitcodes', 'redirect_stderr',
2810-
'environment'):
2872+
'environment', 'prsetpdeathsig'):
28112873
defaults[name] = name
28122874
for name in ('stdout_logfile_backups', 'stdout_logfile_maxbytes',
28132875
'stderr_logfile_backups', 'stderr_logfile_maxbytes'):

supervisor/tests/test_supervisord.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,7 @@ def make_pconfig(name, command, **params):
332332
'stopasgroup': False,
333333
'killasgroup': False,
334334
'exitcodes': (0,2), 'environment': None, 'serverurl': None,
335+
'prsetpdeathsig': None
335336
}
336337
result.update(params)
337338
return ProcessConfig(options, **result)

0 commit comments

Comments
 (0)