Browse Source

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.

Samuele Pedroni 14 years ago
parent
commit
f0c3cd4ee8

+ 4 - 1
supervisor/options.py

@@ -744,6 +744,7 @@ class ServerOptions(Options):
         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 @@ class ServerOptions(Options):
                 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):

+ 14 - 2
supervisor/process.py

@@ -351,9 +351,16 @@ class Subprocess:
             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 @@ class Subprocess:
                             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)

+ 2 - 1
supervisor/tests/base.py

@@ -480,7 +480,7 @@ class DummyPConfig:
                  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 @@ class DummyPConfig:
             stopsignal = signal.SIGTERM
         self.stopsignal = stopsignal
         self.stopwaitsecs = stopwaitsecs
+        self.killasgroup = killasgroup
         self.exitcodes = exitcodes
         self.environment = environment
         self.directory = directory

+ 13 - 5
supervisor/tests/test_options.py

@@ -218,6 +218,7 @@ class ServerOptionsTests(unittest.TestCase):
         command=/bin/cat
         autorestart=true
         exitcodes=0,1,127
+        killasgroup=true
         
         [program:cat4]
         priority=4
@@ -275,6 +276,7 @@ class ServerOptionsTests(unittest.TestCase):
         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 @@ class ServerOptionsTests(unittest.TestCase):
         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 @@ class ServerOptionsTests(unittest.TestCase):
         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 @@ class ServerOptionsTests(unittest.TestCase):
         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 @@ class ServerOptionsTests(unittest.TestCase):
         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 @@ class ServerOptionsTests(unittest.TestCase):
         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 @@ class ServerOptionsTests(unittest.TestCase):
         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 @@ class TestProcessConfig(unittest.TestCase):
                      '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 @@ class FastCGIProcessConfigTest(unittest.TestCase):
                      '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)

+ 17 - 0
supervisor/tests/test_process.py

@@ -715,6 +715,23 @@ class SubprocessTests(unittest.TestCase):
         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',

+ 1 - 0
supervisor/tests/test_supervisord.py

@@ -259,6 +259,7 @@ class SupervisordTests(unittest.TestCase):
                 '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)