From: Leandro Lucarella Date: Mon, 12 Nov 2007 20:01:06 +0000 (-0300) Subject: Improve ProcessManager to manage registerable named services. X-Git-Url: https://git.llucax.com/software/pymin.git/commitdiff_plain/26a08f3b6ccc09010dee4d9d5c241b30bc303496?ds=inline Improve ProcessManager to manage registerable named services. Besides call, now ProcessManager can register (and unregister) named services. Once registered, they can be started and stopped (or killed if multiple stop are issued before the process actually dies) at will, just like regular processes. Processes can be signaled too. Documentation is still missing. --- diff --git a/pymin/procman.py b/pymin/procman.py index 423ace0..e0f3017 100644 --- a/pymin/procman.py +++ b/pymin/procman.py @@ -2,31 +2,91 @@ import os import errno +import signal import subprocess class ProcessInfo: - def __init__(self, name, process, args, kw, callback=None, persist=False): + def __init__(self, name, command, callback=None, persist=False, + args=None, kw=None, max_errors=3): self.name = name - self.process = process + self.command = command + self.callback = callback + if args is None: args = list() self.args = args + if kw is None: kw = dict() self.kw = kw - self.callback = callback self.persist = persist + self.max_errors = max_errors + self.clear() + def clear(self): + self.dont_run = False + self.signal = None + self.process = None + self.error_count = 0 + self.last_return = None + self.running = False + def start(self): + assert self.process is None + self.restart() + def restart(self): + self.clear() + self.process = subprocess.Popen(self.command, *self.args, **self.kw) + self.running = True + def stop(self): + assert self.process is not None + self.dont_run = True + if self.signal == signal.SIGTERM or self.signal == signal.SIGKILL: + # Allready stopped, kill it + self.kill(signal.SIGKILL) + else: + # Stop it + self.kill(signal.SIGTERM) + def kill(self, signum): + assert self.process is not None + os.kill(pi.process.pid, signum) + self.signal = signum def __repr__(self): - return 'ProcessInfo(name=%s, pid=%s, persist=%s, cb=%s, args=%s)' % ( - self.name, self.process.pid, self.persist, - self.callback.__name__, self.args) + pid = None + if self.process is not None: + pid = self.process.pid + return 'ProcessInfo(name=%s, pid=%s command=%s, persist=%s, cb=%s)' % ( + self.name, pid, self.command, self.persist, + self.callback.__name__) class ProcessManager: def __init__(self): + self.services = dict() self.namemap = dict() self.pidmap = dict() - def call(self, name, callback, persist, *args, **kw): - proc = subprocess.Popen(*args, **kw) - procinfo = ProcessInfo(name, proc, args, kw, callback, persist) - self.namemap[name] = self.pidmap[proc.pid] = procinfo + def register(self, name, command, callback=None, persist=False, + *args, **kw): + self.services[name] = ProcessInfo(name, command, callback, persist, + args, kw) + + def unregister(self, name): + del self.services[name] + + def _call(self, pi): + pi.start() + self.namemap[pi.name] = self.pidmap[pi.process.pid] = pi + + def call(self, name, command, callback=None, persist=False, *args, **kw): + pi = ProcessInfo(name, command, callback, persist, args, kw) + self._call(pi) + + def start(self, name): + assert name not in self.namemap + self._call(self.services[name]) + + def stop(self, name): + assert name in self.namemap + self.namemap[name].stop(name) + + def kill(self, name, signum): + assert name in self.namemap + self.namemap[name].kill(name, stop) def sigchild_handler(self, signum): try: @@ -38,12 +98,20 @@ class ProcessManager: while pid: if pid in self.pidmap: p = self.pidmap[pid] - del self.namemap[p.name] - del self.pidmap[pid] if p.callback is not None: - p.callback(p) - if p.persist: - self.call(p.name, p.callback, True, *p.args, **p.kw) + p.callback(self, p) + if p.dont_run or not p.persist or p.error_count >= p.max_errors: + del self.namemap[p.name] + del self.pidmap[pid] + p.clear() + else: + if p.process.returncode == 0: + p.error_count = 0 + else: + p.error_count += 1 + del self.pidmap[pid] + p.restart() + self.pidmap[p.process.pid] = p try: (pid, status) = os.waitpid(-1, os.WNOHANG) except OSError, e: @@ -53,15 +121,25 @@ class ProcessManager: def __getitem__(self, name): if isinstance(name, basestring): # is a name - return self.namemap[name] + if name in self.namemap: + return self.namemap[name] + if name in self.services: + return self.services[name] else: # is a pid - return self.pidmap[name] + if name in self.pidmap: + return self.pidmap[name] + return KeyError, name def __contains__(self, name): if isinstance(name, basestring): # is a name - return name in self.namemap + if name in self.namemap: + return True + if name in self.services: + return True else: # is a pid - return name in self.pidmap + if name in self.pidmap: + return True + return False if __name__ == '__main__': @@ -70,29 +148,38 @@ if __name__ == '__main__': import time sig = None + count = 0 def sigchild_handler(signum, stacktrace): global sig sig = signum print 'SIGCHLD', signum - def test_notify(proc): - print 'test died:', proc, proc.name, proc.process.pid + def notify(pm, pi): + global count + if pi.name == 'test-service': + print 'test-service count =', count + count += 1 + if count > 4: + print 'test-service not persistent anymore, start test2' + pi.persist = False + pm.start('test2') + print 'died:', pi.name, pi.command procman = ProcessManager() + procman.register('test-service', ('sleep', '2'), notify, True) + procman.register('test2', ('sleep', '3'), notify, False) + signal.signal(signal.SIGCHLD, sigchild_handler) - procman.call('test', test_notify, True, ('sleep', '5')) + procman.call('test', ('sleep', '5'), notify) + procman.start('test-service') - while True: + while procman.pidmap: time.sleep(1) - print "Esperando...", - if 'test' in procman: - print procman['test'] - else: - print if sig == signal.SIGCHLD: sig = None procman.sigchild_handler(sig) + print "Esperando...", [pi.name for pi in procman.namemap.values()]