浏览代码

Merge branch 'master' of github.com:Supervisor/supervisor

Mike Naberezny 14 年之前
父节点
当前提交
7bfc865032

+ 5 - 0
CHANGES.txt

@@ -28,6 +28,11 @@ Next release
   supervisord is run as root, otherwise the error is printed as before.
   supervisord is run as root, otherwise the error is printed as before.
   Patch by Benoit Sigoure.
   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)
 3.0a10 (2011-03-30)
 -------------------
 -------------------
 
 

+ 13 - 0
docs/configuration.rst

@@ -712,6 +712,19 @@ where specified.
 
 
   *Introduced*: 3.0
   *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``
 ``user``
 
 
   If :program:`supervisord` runs as root, this UNIX user account will
   If :program:`supervisord` runs as root, this UNIX user account will

+ 4 - 1
supervisor/options.py

@@ -744,6 +744,7 @@ class ServerOptions(Options):
         uid = name_to_uid(get(section, 'user', None))
         uid = name_to_uid(get(section, 'user', None))
         stopsignal = signal_number(get(section, 'stopsignal', 'TERM'))
         stopsignal = signal_number(get(section, 'stopsignal', 'TERM'))
         stopwaitsecs = integer(get(section, 'stopwaitsecs', 10))
         stopwaitsecs = integer(get(section, 'stopwaitsecs', 10))
+        killasgroup = boolean(get(section, 'killasgroup', 'false'))
         exitcodes = list_of_exitcodes(get(section, 'exitcodes', '0,2'))
         exitcodes = list_of_exitcodes(get(section, 'exitcodes', '0,2'))
         redirect_stderr = boolean(get(section, 'redirect_stderr','false'))
         redirect_stderr = boolean(get(section, 'redirect_stderr','false'))
         numprocs = integer(get(section, 'numprocs', 1))
         numprocs = integer(get(section, 'numprocs', 1))
@@ -837,6 +838,7 @@ class ServerOptions(Options):
                 stderr_logfile_maxbytes=logfiles['stderr_logfile_maxbytes'],
                 stderr_logfile_maxbytes=logfiles['stderr_logfile_maxbytes'],
                 stopsignal=stopsignal,
                 stopsignal=stopsignal,
                 stopwaitsecs=stopwaitsecs,
                 stopwaitsecs=stopwaitsecs,
+                killasgroup=killasgroup,
                 exitcodes=exitcodes,
                 exitcodes=exitcodes,
                 redirect_stderr=redirect_stderr,
                 redirect_stderr=redirect_stderr,
                 environment=environment,
                 environment=environment,
@@ -1545,7 +1547,8 @@ class ProcessConfig(Config):
         'stderr_logfile', 'stderr_capture_maxbytes', 
         'stderr_logfile', 'stderr_capture_maxbytes', 
         'stderr_logfile_backups', 'stderr_logfile_maxbytes',
         'stderr_logfile_backups', 'stderr_logfile_maxbytes',
         'stderr_events_enabled',
         'stderr_events_enabled',
-        'stopsignal', 'stopwaitsecs', 'exitcodes', 'redirect_stderr' ]
+        'stopsignal', 'stopwaitsecs', 'killasgroup',
+        'exitcodes', 'redirect_stderr' ]
     optional_param_names = [ 'environment', 'serverurl' ]
     optional_param_names = [ 'environment', 'serverurl' ]
 
 
     def __init__(self, options, **params):
     def __init__(self, options, **params):

+ 14 - 2
supervisor/process.py

@@ -351,9 +351,16 @@ class Subprocess:
             options.logger.debug(msg)
             options.logger.debug(msg)
             return 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.config.name,
                                 self.pid,
                                 self.pid,
+                                as_group,
                                 signame(sig))
                                 signame(sig))
                              )
                              )
 
 
@@ -366,8 +373,13 @@ class Subprocess:
                             ProcessStates.STOPPING)
                             ProcessStates.STOPPING)
         self.change_state(ProcessStates.STOPPING)
         self.change_state(ProcessStates.STOPPING)
 
 
+        pid = self.pid
+        if killasgroup:
+            # send to the whole process group instead
+            pid = -self.pid
+
         try:
         try:
-            options.kill(self.pid, sig)
+            options.kill(pid, sig)
         except:
         except:
             io = StringIO.StringIO()
             io = StringIO.StringIO()
             traceback.print_exc(file=io)
             traceback.print_exc(file=io)

+ 2 - 1
supervisor/tests/base.py

@@ -480,7 +480,7 @@ class DummyPConfig:
                  stderr_events_enabled=False,
                  stderr_events_enabled=False,
                  stderr_logfile_backups=0, stderr_logfile_maxbytes=0,
                  stderr_logfile_backups=0, stderr_logfile_maxbytes=0,
                  redirect_stderr=False,
                  redirect_stderr=False,
-                 stopsignal=None, stopwaitsecs=10,
+                 stopsignal=None, stopwaitsecs=10, killasgroup=False,
                  exitcodes=(0,2), environment=None, serverurl=None):
                  exitcodes=(0,2), environment=None, serverurl=None):
         self.options = options
         self.options = options
         self.name = name
         self.name = name
@@ -507,6 +507,7 @@ class DummyPConfig:
             stopsignal = signal.SIGTERM
             stopsignal = signal.SIGTERM
         self.stopsignal = stopsignal
         self.stopsignal = stopsignal
         self.stopwaitsecs = stopwaitsecs
         self.stopwaitsecs = stopwaitsecs
+        self.killasgroup = killasgroup
         self.exitcodes = exitcodes
         self.exitcodes = exitcodes
         self.environment = environment
         self.environment = environment
         self.directory = directory
         self.directory = directory

+ 13 - 5
supervisor/tests/test_options.py

@@ -218,6 +218,7 @@ class ServerOptionsTests(unittest.TestCase):
         command=/bin/cat
         command=/bin/cat
         autorestart=true
         autorestart=true
         exitcodes=0,1,127
         exitcodes=0,1,127
+        killasgroup=true
         
         
         [program:cat4]
         [program:cat4]
         priority=4
         priority=4
@@ -275,6 +276,7 @@ class ServerOptionsTests(unittest.TestCase):
         self.assertEqual(proc1.stdout_logfile, '/tmp/cat.log')
         self.assertEqual(proc1.stdout_logfile, '/tmp/cat.log')
         self.assertEqual(proc1.stopsignal, signal.SIGKILL)
         self.assertEqual(proc1.stopsignal, signal.SIGKILL)
         self.assertEqual(proc1.stopwaitsecs, 5)
         self.assertEqual(proc1.stopwaitsecs, 5)
+        self.assertEqual(proc1.killasgroup, False)
         self.assertEqual(proc1.stdout_logfile_maxbytes,
         self.assertEqual(proc1.stdout_logfile_maxbytes,
                          datatypes.byte_size('50MB'))
                          datatypes.byte_size('50MB'))
         self.assertEqual(proc1.stdout_logfile_backups, 10)
         self.assertEqual(proc1.stdout_logfile_backups, 10)
@@ -297,6 +299,7 @@ class ServerOptionsTests(unittest.TestCase):
         self.assertEqual(proc2.uid, None)
         self.assertEqual(proc2.uid, None)
         self.assertEqual(proc2.stdout_logfile, '/tmp/cat2.log')
         self.assertEqual(proc2.stdout_logfile, '/tmp/cat2.log')
         self.assertEqual(proc2.stopsignal, signal.SIGTERM)
         self.assertEqual(proc2.stopsignal, signal.SIGTERM)
+        self.assertEqual(proc2.killasgroup, False)
         self.assertEqual(proc2.stdout_logfile_maxbytes, 1024)
         self.assertEqual(proc2.stdout_logfile_maxbytes, 1024)
         self.assertEqual(proc2.stdout_logfile_backups, 2)
         self.assertEqual(proc2.stdout_logfile_backups, 2)
         self.assertEqual(proc2.exitcodes, [0,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.stdout_logfile_backups, 10)
         self.assertEqual(proc3.exitcodes, [0,1,127])
         self.assertEqual(proc3.exitcodes, [0,1,127])
         self.assertEqual(proc3.stopsignal, signal.SIGTERM)
         self.assertEqual(proc3.stopsignal, signal.SIGTERM)
-
+        self.assertEqual(proc3.killasgroup, True)
+        
         cat4 = options.process_group_configs[3]
         cat4 = options.process_group_configs[3]
         self.assertEqual(cat4.name, 'cat4')
         self.assertEqual(cat4.name, 'cat4')
         self.assertEqual(cat4.priority, 4)
         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.stdout_logfile_backups, 10)
         self.assertEqual(proc4_a.exitcodes, [0,2])
         self.assertEqual(proc4_a.exitcodes, [0,2])
         self.assertEqual(proc4_a.stopsignal, signal.SIGTERM)
         self.assertEqual(proc4_a.stopsignal, signal.SIGTERM)
-
+        self.assertEqual(proc4_a.killasgroup, False)
+        
         proc4_b = cat4.process_configs[1]
         proc4_b = cat4.process_configs[1]
         self.assertEqual(proc4_b.name, 'fleeb_1')
         self.assertEqual(proc4_b.name, 'fleeb_1')
         self.assertEqual(proc4_b.command, '/bin/cat')
         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.stdout_logfile_backups, 10)
         self.assertEqual(proc4_b.exitcodes, [0,2])
         self.assertEqual(proc4_b.exitcodes, [0,2])
         self.assertEqual(proc4_b.stopsignal, signal.SIGTERM)
         self.assertEqual(proc4_b.stopsignal, signal.SIGTERM)
-
+        self.assertEqual(proc4_b.killasgroup, False)
+        
         here = os.path.abspath(os.getcwd())
         here = os.path.abspath(os.getcwd())
         self.assertEqual(instance.uid, 0)
         self.assertEqual(instance.uid, 0)
         self.assertEqual(instance.gid, 0)
         self.assertEqual(instance.gid, 0)
@@ -597,6 +603,7 @@ class ServerOptionsTests(unittest.TestCase):
         stdout_events_enabled = true
         stdout_events_enabled = true
         stopsignal = KILL
         stopsignal = KILL
         stopwaitsecs = 100
         stopwaitsecs = 100
+        killasgroup = true
         exitcodes = 1,4
         exitcodes = 1,4
         redirect_stderr = false
         redirect_stderr = false
         environment = KEY1=val1,KEY2=val2,KEY3=%(process_num)s
         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_logfile_maxbytes, 104857600)
         self.assertEqual(pconfig.stdout_events_enabled, True)
         self.assertEqual(pconfig.stdout_events_enabled, True)
         self.assertEqual(pconfig.stopsignal, signal.SIGKILL)
         self.assertEqual(pconfig.stopsignal, signal.SIGKILL)
+        self.assertEqual(pconfig.killasgroup, True)
         self.assertEqual(pconfig.stopwaitsecs, 100)
         self.assertEqual(pconfig.stopwaitsecs, 100)
         self.assertEqual(pconfig.exitcodes, [1,4])
         self.assertEqual(pconfig.exitcodes, [1,4])
         self.assertEqual(pconfig.redirect_stderr, False)
         self.assertEqual(pconfig.redirect_stderr, False)
@@ -1295,7 +1303,7 @@ class TestProcessConfig(unittest.TestCase):
                      'stderr_logfile', 'stderr_capture_maxbytes',
                      'stderr_logfile', 'stderr_capture_maxbytes',
                      'stderr_events_enabled',
                      'stderr_events_enabled',
                      'stderr_logfile_backups', 'stderr_logfile_maxbytes',
                      'stderr_logfile_backups', 'stderr_logfile_maxbytes',
-                     'stopsignal', 'stopwaitsecs', 'exitcodes',
+                     'stopsignal', 'stopwaitsecs', 'killasgroup', 'exitcodes',
                      'redirect_stderr', 'environment'):
                      'redirect_stderr', 'environment'):
             defaults[name] = name
             defaults[name] = name
         defaults.update(kw)
         defaults.update(kw)
@@ -1369,7 +1377,7 @@ class FastCGIProcessConfigTest(unittest.TestCase):
                      'stderr_logfile', 'stderr_capture_maxbytes',        
                      'stderr_logfile', 'stderr_capture_maxbytes',        
                      'stderr_events_enabled',
                      'stderr_events_enabled',
                      'stderr_logfile_backups', 'stderr_logfile_maxbytes',
                      'stderr_logfile_backups', 'stderr_logfile_maxbytes',
-                     'stopsignal', 'stopwaitsecs', 'exitcodes',
+                     'stopsignal', 'stopwaitsecs', 'killasgroup', 'exitcodes',
                      'redirect_stderr', 'environment'):
                      'redirect_stderr', 'environment'):
             defaults[name] = name
             defaults[name] = name
         defaults.update(kw)
         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(options.kills[11], signal.SIGKILL)
         self.assertEqual(L, []) # no event because we didn't change state
         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):
     def test_finish(self):
         options = DummyOptions()
         options = DummyOptions()
         config = DummyPConfig(options, 'notthere', '/notthere',
         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,
                 'stderr_logfile_backups': 0, 'stderr_logfile_maxbytes': 0,
                 'redirect_stderr': False,
                 'redirect_stderr': False,
                 'stopsignal': None, 'stopwaitsecs': 10,
                 'stopsignal': None, 'stopwaitsecs': 10,
+                'killasgroup': False,
                 'exitcodes': (0,2), 'environment': None, 'serverurl': None }
                 'exitcodes': (0,2), 'environment': None, 'serverurl': None }
             result.update(params)
             result.update(params)
             return ProcessConfig(options, **result)
             return ProcessConfig(options, **result)