]> git.llucax.com Git - software/pymin.git/commitdiff
Improve ProcessManager to manage registerable named services.
authorLeandro Lucarella <llucax@gmail.com>
Mon, 12 Nov 2007 20:01:06 +0000 (17:01 -0300)
committerLeandro Lucarella <llucax@gmail.com>
Mon, 12 Nov 2007 20:01:06 +0000 (17:01 -0300)
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.

pymin/procman.py

index 423ace010cb647720443ee4cf85821ef3c123f05..e0f3017976bb5b112ccfba8cc0d863998ced1f33 100644 (file)
@@ -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()]