X-Git-Url: https://git.llucax.com/software/pymin.git/blobdiff_plain/c6010d17b74d9b70ba68b8f4567229c92a868f46..9c443d159056e112ed23af07afa38022882583b3:/pymin/procman.py 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()]