Преглед изворни кода

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

Mike Naberezny пре 13 година
родитељ
комит
7bfc865032

+ 5 - 0
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)
 -------------------
 

+ 13 - 0
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

+ 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)