Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

when resorting to SIGKILL send signal (optionally) to whole process group #30

Merged
merged 2 commits into from

3 participants

@pedronis

add a boolean program option killasgroup with tests, defaulting to
false, if true when resorting to send SIGKILL to stop/terminate the
process send it to its whole process group instead to take care of
children as well and not leave them behind. comes with docs changes as well.

see mailing list thread starting with: http://lists.supervisord.org/pipermail/supervisor-users/2011-August/000946.html

pedronis added some commits
@pedronis pedronis add a boolean program option killasgroup with tests, defaulting to
false, if true when resorting to send SIGKILL to stop/terminate the
process send it to its whole process group instead to take care of
children as well and not leave them behind.
f0c3cd4
@pedronis pedronis update docs and changes for killasgroup ba7391e
@mnaberez mnaberez merged commit 1d7e0a2 into Supervisor:master
@theduderog
Owner

Is there a reason not to send SIGTERM to the whole group as well?

@mnaberez
Owner

Not that I can see... this is something we should consider.

@pedronis
@theduderog
Owner

I think this should be another option, then, called stopasgroup so that you can turn it on if you need it. I've run into a case where I want to run Flask in debug mode under supervisord in development and the parent Flask process doesn't propagate the signal to it's child, leaving it orphaned. This doesn't happen on the command line b/c the shell sends SIGINT to the entire foreground process group.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Aug 12, 2011
  1. @pedronis

    add a boolean program option killasgroup with tests, defaulting to

    pedronis authored
    false, if true when resorting to send SIGKILL to stop/terminate the
    process send it to its whole process group instead to take care of
    children as well and not leave them behind.
  2. @pedronis
This page is out of date. Refresh to see the latest.
View
5 CHANGES.txt
@@ -28,6 +28,11 @@ Next release
supervisord is run as root, otherwise the error is printed as before.
Patch by Benoit Sigoure.
+- Add a boolean program option ``killasgroup``, defaulting to false,
+ if true when resorting to send SIGKILL to stop/terminate the process
+ send it to its whole process group instead to take care of possible
+ children as well and not leave them behind.
+
3.0a10 (2011-03-30)
-------------------
View
13 docs/configuration.rst
@@ -712,6 +712,19 @@ where specified.
*Introduced*: 3.0
+``killasgroup``
+
+ If true, when resorting to send SIGKILL to the program to terminate
+ it send it to its whole process group instead, taking care of its
+ children as well, useful e.g with Python programs using
+ :mod:`multiprocessing`.
+
+ *Default*: false
+
+ *Required*: No.
+
+ *Introduced*: 3.0a11
+
``user``
If :program:`supervisord` runs as root, this UNIX user account will
View
5 supervisor/options.py
@@ -744,6 +744,7 @@ def processes_from_section(self, parser, section, group_name,
uid = name_to_uid(get(section, 'user', None))
stopsignal = signal_number(get(section, 'stopsignal', 'TERM'))
stopwaitsecs = integer(get(section, 'stopwaitsecs', 10))
+ killasgroup = boolean(get(section, 'killasgroup', 'false'))
exitcodes = list_of_exitcodes(get(section, 'exitcodes', '0,2'))
redirect_stderr = boolean(get(section, 'redirect_stderr','false'))
numprocs = integer(get(section, 'numprocs', 1))
@@ -837,6 +838,7 @@ def processes_from_section(self, parser, section, group_name,
stderr_logfile_maxbytes=logfiles['stderr_logfile_maxbytes'],
stopsignal=stopsignal,
stopwaitsecs=stopwaitsecs,
+ killasgroup=killasgroup,
exitcodes=exitcodes,
redirect_stderr=redirect_stderr,
environment=environment,
@@ -1545,7 +1547,8 @@ class ProcessConfig(Config):
'stderr_logfile', 'stderr_capture_maxbytes',
'stderr_logfile_backups', 'stderr_logfile_maxbytes',
'stderr_events_enabled',
- 'stopsignal', 'stopwaitsecs', 'exitcodes', 'redirect_stderr' ]
+ 'stopsignal', 'stopwaitsecs', 'killasgroup',
+ 'exitcodes', 'redirect_stderr' ]
optional_param_names = [ 'environment', 'serverurl' ]
def __init__(self, options, **params):
View
16 supervisor/process.py
@@ -351,9 +351,16 @@ def kill(self, sig):
options.logger.debug(msg)
return msg
- options.logger.debug('killing %s (pid %s) with signal %s'
+ killasgroup = self.config.killasgroup and sig == signal.SIGKILL
+
+ as_group = ""
+ if killasgroup:
+ as_group = "process group "
+
+ options.logger.debug('killing %s (pid %s) %swith signal %s'
% (self.config.name,
self.pid,
+ as_group,
signame(sig))
)
@@ -366,8 +373,13 @@ def kill(self, sig):
ProcessStates.STOPPING)
self.change_state(ProcessStates.STOPPING)
+ pid = self.pid
+ if killasgroup:
+ # send to the whole process group instead
+ pid = -self.pid
+
try:
- options.kill(self.pid, sig)
+ options.kill(pid, sig)
except:
io = StringIO.StringIO()
traceback.print_exc(file=io)
View
3  supervisor/tests/base.py
@@ -480,7 +480,7 @@ def __init__(self, options, name, command, directory=None, umask=None,
stderr_events_enabled=False,
stderr_logfile_backups=0, stderr_logfile_maxbytes=0,
redirect_stderr=False,
- stopsignal=None, stopwaitsecs=10,
+ stopsignal=None, stopwaitsecs=10, killasgroup=False,
exitcodes=(0,2), environment=None, serverurl=None):
self.options = options
self.name = name
@@ -507,6 +507,7 @@ def __init__(self, options, name, command, directory=None, umask=None,
stopsignal = signal.SIGTERM
self.stopsignal = stopsignal
self.stopwaitsecs = stopwaitsecs
+ self.killasgroup = killasgroup
self.exitcodes = exitcodes
self.environment = environment
self.directory = directory
View
18 supervisor/tests/test_options.py
@@ -218,6 +218,7 @@ def test_options(self):
command=/bin/cat
autorestart=true
exitcodes=0,1,127
+ killasgroup=true
[program:cat4]
priority=4
@@ -275,6 +276,7 @@ def test_options(self):
self.assertEqual(proc1.stdout_logfile, '/tmp/cat.log')
self.assertEqual(proc1.stopsignal, signal.SIGKILL)
self.assertEqual(proc1.stopwaitsecs, 5)
+ self.assertEqual(proc1.killasgroup, False)
self.assertEqual(proc1.stdout_logfile_maxbytes,
datatypes.byte_size('50MB'))
self.assertEqual(proc1.stdout_logfile_backups, 10)
@@ -297,6 +299,7 @@ def test_options(self):
self.assertEqual(proc2.uid, None)
self.assertEqual(proc2.stdout_logfile, '/tmp/cat2.log')
self.assertEqual(proc2.stopsignal, signal.SIGTERM)
+ self.assertEqual(proc2.killasgroup, False)
self.assertEqual(proc2.stdout_logfile_maxbytes, 1024)
self.assertEqual(proc2.stdout_logfile_backups, 2)
self.assertEqual(proc2.exitcodes, [0,2])
@@ -320,7 +323,8 @@ def test_options(self):
self.assertEqual(proc3.stdout_logfile_backups, 10)
self.assertEqual(proc3.exitcodes, [0,1,127])
self.assertEqual(proc3.stopsignal, signal.SIGTERM)
-
+ self.assertEqual(proc3.killasgroup, True)
+
cat4 = options.process_group_configs[3]
self.assertEqual(cat4.name, 'cat4')
self.assertEqual(cat4.priority, 4)
@@ -340,7 +344,8 @@ def test_options(self):
self.assertEqual(proc4_a.stdout_logfile_backups, 10)
self.assertEqual(proc4_a.exitcodes, [0,2])
self.assertEqual(proc4_a.stopsignal, signal.SIGTERM)
-
+ self.assertEqual(proc4_a.killasgroup, False)
+
proc4_b = cat4.process_configs[1]
self.assertEqual(proc4_b.name, 'fleeb_1')
self.assertEqual(proc4_b.command, '/bin/cat')
@@ -355,7 +360,8 @@ def test_options(self):
self.assertEqual(proc4_b.stdout_logfile_backups, 10)
self.assertEqual(proc4_b.exitcodes, [0,2])
self.assertEqual(proc4_b.stopsignal, signal.SIGTERM)
-
+ self.assertEqual(proc4_b.killasgroup, False)
+
here = os.path.abspath(os.getcwd())
self.assertEqual(instance.uid, 0)
self.assertEqual(instance.gid, 0)
@@ -597,6 +603,7 @@ def test_processes_from_section(self):
stdout_events_enabled = true
stopsignal = KILL
stopwaitsecs = 100
+ killasgroup = true
exitcodes = 1,4
redirect_stderr = false
environment = KEY1=val1,KEY2=val2,KEY3=%(process_num)s
@@ -621,6 +628,7 @@ def test_processes_from_section(self):
self.assertEqual(pconfig.stdout_logfile_maxbytes, 104857600)
self.assertEqual(pconfig.stdout_events_enabled, True)
self.assertEqual(pconfig.stopsignal, signal.SIGKILL)
+ self.assertEqual(pconfig.killasgroup, True)
self.assertEqual(pconfig.stopwaitsecs, 100)
self.assertEqual(pconfig.exitcodes, [1,4])
self.assertEqual(pconfig.redirect_stderr, False)
@@ -1295,7 +1303,7 @@ def _makeOne(self, *arg, **kw):
'stderr_logfile', 'stderr_capture_maxbytes',
'stderr_events_enabled',
'stderr_logfile_backups', 'stderr_logfile_maxbytes',
- 'stopsignal', 'stopwaitsecs', 'exitcodes',
+ 'stopsignal', 'stopwaitsecs', 'killasgroup', 'exitcodes',
'redirect_stderr', 'environment'):
defaults[name] = name
defaults.update(kw)
@@ -1369,7 +1377,7 @@ def _makeOne(self, *arg, **kw):
'stderr_logfile', 'stderr_capture_maxbytes',
'stderr_events_enabled',
'stderr_logfile_backups', 'stderr_logfile_maxbytes',
- 'stopsignal', 'stopwaitsecs', 'exitcodes',
+ 'stopsignal', 'stopwaitsecs', 'killasgroup', 'exitcodes',
'redirect_stderr', 'environment'):
defaults[name] = name
defaults.update(kw)
View
17 supervisor/tests/test_process.py
@@ -715,6 +715,23 @@ def test_kill_from_stopping(self):
self.assertEqual(options.kills[11], signal.SIGKILL)
self.assertEqual(L, []) # no event because we didn't change state
+ def test_kill_from_stopping_w_killasgroup(self):
+ options = DummyOptions()
+ config = DummyPConfig(options, 'test', '/test', killasgroup=True)
+ instance = self._makeOne(config)
+ instance.pid = 11
+ L = []
+ from supervisor.states import ProcessStates
+ from supervisor import events
+ events.subscribe(events.Event,lambda x: L.append(x))
+ instance.state = ProcessStates.STOPPING
+ instance.kill(signal.SIGKILL)
+ self.assertEqual(options.logger.data[0], 'killing test (pid 11) '
+ 'process group with signal SIGKILL')
+ self.assertEqual(instance.killing, 1)
+ self.assertEqual(options.kills[-11], signal.SIGKILL)
+ self.assertEqual(L, []) # no event because we didn't change state
+
def test_finish(self):
options = DummyOptions()
config = DummyPConfig(options, 'notthere', '/notthere',
View
1  supervisor/tests/test_supervisord.py
@@ -259,6 +259,7 @@ def make_pconfig(name, command, **params):
'stderr_logfile_backups': 0, 'stderr_logfile_maxbytes': 0,
'redirect_stderr': False,
'stopsignal': None, 'stopwaitsecs': 10,
+ 'killasgroup': False,
'exitcodes': (0,2), 'environment': None, 'serverurl': None }
result.update(params)
return ProcessConfig(options, **result)
Something went wrong with that request. Please try again.