├── .gitignore ├── README.rst ├── setup.py └── supervisor_quick.py /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | *.py[cod] 3 | 4 | # C extensions 5 | *.so 6 | 7 | # Packages 8 | *.egg 9 | *.egg-info 10 | dist 11 | build 12 | eggs 13 | parts 14 | bin 15 | var 16 | sdist 17 | develop-eggs 18 | .installed.cfg 19 | lib 20 | lib64 21 | __pycache__ 22 | 23 | # Installer logs 24 | pip-log.txt 25 | 26 | # Unit test / coverage reports 27 | .coverage 28 | .tox 29 | nosetests.xml 30 | 31 | # Translations 32 | *.mo 33 | 34 | # Mr Developer 35 | .mr.developer.cfg 36 | .project 37 | .pydevproject 38 | *.sublime-workspace 39 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Supervisor-Quick 2 | ================ 3 | 4 | Bypass supervisor's nasty callbacks stack and make it quick! 5 | 6 | 7 | Usage 8 | ----- 9 | 10 | .. code:: bash 11 | 12 | $ pip install supervisor-quick 13 | 14 | And add the following config to `supervisord.conf`. 15 | 16 | .. code:: ini 17 | 18 | [ctlplugin:quick] 19 | supervisor.ctl_factory = supervisor_quick:make_quick_controllerplugin 20 | 21 | Then start `supervisorctl` and use `quickstart`, `quickstop` and 22 | `quickrestart` to start/stop/restart processes. 23 | 24 | .. code:: 25 | 26 | > quickstart app:0 27 | > quickstart app: 28 | > quickstart ap* 29 | > quickstart all 30 | 31 | > quickstop app:1 32 | > quickstop app: 33 | > quickstop ap* 34 | > quickstop all 35 | 36 | > quickrestart app:2 37 | > quickrestart app: 38 | > quickrestart ap* 39 | > quickrestart all 40 | 41 | It effects `supervisorctl`, so you don't have to restart the whole 42 | supervisord to make it work. 43 | 44 | 45 | Why 46 | --- 47 | 48 | I write this plugin because supervisor is just tooooo slow in 49 | start/stop/restart app server in our prod servers. 50 | 51 | And I checked the source code and found it is because of the 52 | nasty callbacks stack, and this is a quote from source code 53 | `supervisor/rpcinterface.py`:: 54 | 55 | # XXX the above implementation has a weakness inasmuch as the 56 | # first call into each individual process callback will always 57 | # return NOT_DONE_YET, so they need to be called twice. The 58 | # symptom of this is that calling this method causes the 59 | # client to block for much longer than it actually requires to 60 | # kill all of the running processes. After the first call to 61 | # the killit callback, the process is actually dead, but the 62 | # above killall method processes the callbacks one at a time 63 | # during the select loop, which, because there is no output 64 | # from child processes after e.g. stopAllProcesses is called, 65 | # is not busy, so hits the timeout for each callback. I 66 | # attempted to make this better, but the only way to make it 67 | # better assumes totally synchronous reaping of child 68 | # processes, which requires infrastructure changes to 69 | # supervisord that are scary at the moment as it could take a 70 | # while to pin down all of the platform differences and might 71 | # require a C extension to the Python signal module to allow 72 | # the setting of ignore flags to signals. 73 | 74 | And this plugin will do a `quick` start/stop/restart action that bypass 75 | all the callback checks, making it lightning fast. 76 | 77 | It also have wildcard concurrent execution support, keeping it fast 78 | regardless of processes amount. (This function is inspired by 79 | `supervisor-wildcards `_) 80 | 81 | 82 | Example 83 | ------- 84 | 85 | An example time demo for a app server with numprocs set to 32 to show how quick 86 | supervisor can be with `quick` command. 87 | 88 | .. code:: bash 89 | 90 | $ supervisorctl status 91 | app:0 STOPPED 92 | app:1 STOPPED 93 | app:10 STOPPED 94 | ...... 95 | app:7 STOPPED 96 | app:8 STOPPED 97 | app:9 STOPPED 98 | 99 | $ time supervisorctl start app: 100 | 24: started 101 | 25: started 102 | 26: started 103 | ...... 104 | 18: started 105 | 31: started 106 | 30: started 107 | supervisorctl start app: 0.06s user 0.02s system 0% cpu 48.442 total 108 | 109 | $ time supervisorctl stop app: 110 | 24: stopped 111 | 25: stopped 112 | 26: stopped 113 | ...... 114 | 18: stopped 115 | 31: stopped 116 | 30: stopped 117 | supervisorctl stop app: 0.06s user 0.03s system 0% cpu 36.278 total 118 | 119 | $ time supervisorctl quickstart app: 120 | app:25: started 121 | app:24: started 122 | app:27: started 123 | ...... 124 | app:1: started 125 | app:8: started 126 | app:9: started 127 | supervisorctl quickstart app: 0.09s user 0.03s system 19% cpu 0.618 total 128 | 129 | $ time supervisorctl quickstop app: 130 | app:26: stoped 131 | app:27: stoped 132 | app:22: stoped 133 | ...... 134 | app:0: stoped 135 | app:9: stoped 136 | app:8: stoped 137 | supervisorctl quickstop app: 0.09s user 0.04s system 68% cpu 0.196 total 138 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from setuptools import setup 5 | 6 | setup( 7 | name="supervisor-quick", 8 | version=__import__("supervisor_quick").__version__, 9 | description="Bypass supervisor's nasty callbacks stack and make it quick!", 10 | author="Lx Yu", 11 | author_email="i@lxyu.net", 12 | py_modules=["supervisor_quick", ], 13 | package_data={"": ["LICENSE"], }, 14 | url="http://lxyu.github.io/supervisor-quick/", 15 | license="MIT", 16 | long_description=open("README.rst").read(), 17 | install_requires=[ 18 | "supervisor", 19 | ], 20 | ) 21 | -------------------------------------------------------------------------------- /supervisor_quick.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | __version__ = "0.1.4" 4 | 5 | import fnmatch 6 | import threading 7 | import time 8 | import xmlrpclib 9 | 10 | from supervisor.supervisorctl import ControllerPluginBase 11 | 12 | 13 | class QuickControllerPlugin(ControllerPluginBase): 14 | name = "quick" 15 | 16 | def __init__(self, controller, retries=600, **config): 17 | self.ctl = controller 18 | self.retries = retries 19 | 20 | def _quick_do(self, arg, command): 21 | assert command in ("start", "stop") 22 | 23 | patterns = arg.strip().split() 24 | if not patterns: 25 | return self.ctl.output('No process matched given expression.') 26 | 27 | # compatible with group 28 | patterns = [p + '*' if p.endswith(':') else p for p in patterns] 29 | 30 | # if 'all' pattern exists, match all. 31 | if "all" in patterns or '*' in patterns: 32 | patterns = ['*'] 33 | 34 | processes = set() 35 | for p in self.ctl.get_supervisor().getAllProcessInfo(): 36 | p_name = "{0}:{1}".format(p["group"], p["name"]) 37 | for pattern in patterns: 38 | if fnmatch.fnmatch(p_name, pattern): 39 | processes.add(p_name) 40 | break 41 | 42 | def _do(process): 43 | supervisor = self.ctl.get_supervisor() 44 | _command = getattr(supervisor, "{0}Process".format(command)) 45 | try: 46 | _command(process, False) 47 | except xmlrpclib.Fault as e: 48 | return self.ctl.output("{0} ERROR({1})".format( 49 | process, e.faultString.split(':')[0])) 50 | 51 | # state check 52 | state = "RUNNING" if command is "start" else "STOPPED" 53 | count = self.retries 54 | while count: 55 | current_state = supervisor.getProcessInfo(process)['statename'] 56 | if state == current_state: 57 | return self.ctl.output("{0}: {1}".format(process, state)) 58 | time.sleep(0.1) 59 | count -= 1 60 | return self.ctl.output("{0}: {1}".format(process, current_state)) 61 | 62 | threads = [] 63 | for p in processes: 64 | # set wait to False to do it quick 65 | t = threading.Thread(target=_do, args=(p,), name=p) 66 | t.start() 67 | threads.append(t) 68 | 69 | for t in threads: 70 | t.join() 71 | 72 | def do_quickstop(self, arg): 73 | self._quick_do(arg, command='stop') 74 | 75 | def do_quickstart(self, arg): 76 | self._quick_do(arg, command='start') 77 | 78 | def do_quickrestart(self, arg): 79 | self._quick_do(arg, command='stop') 80 | self._quick_do(arg, command='start') 81 | 82 | 83 | def make_quick_controllerplugin(controller, **config): 84 | return QuickControllerPlugin(controller, **config) 85 | --------------------------------------------------------------------------------