]> git.llucax.com Git - software/pymin.git/commitdiff
Add support for a simple timer to do periodic tasks.
authorLeandro Lucarella <llucax@gmail.com>
Wed, 24 Oct 2007 20:41:28 +0000 (17:41 -0300)
committerLeandro Lucarella <llucax@gmail.com>
Wed, 24 Oct 2007 20:41:28 +0000 (17:41 -0300)
Base support is on EventLoop, who handles the timer
life and calls handle_timer() when the timer is
expired. dispatcher.Handler has a simple default
handle_timer() implementation that just promote the
"handle_timer message" to all subhandlers, while
PyDaemon just start "spreading the voice" by calling
the root handler handle_timer() method.

pymin/dispatcher.py
pymin/eventloop.py
pymin/pymindaemon.py

index 17075a3f1dfec8617432dd68fb532022f7299b2b..24f0d1f14e6a52b6f97fd49664d767b07382a99a 100644 (file)
@@ -216,6 +216,17 @@ class Handler:
             raise HelpNotFoundError(command)
         return handler.handler_help
 
+    def handle_timer(self):
+        r"""handle_timer() -> None :: Do periodic tasks.
+
+        By default we do nothing but calling handle_timer() on subhandlers.
+        """
+        for a in dir(self):
+            if a == 'parent': continue # Skip parents in SubHandlers
+            h = getattr(self, a)
+            if isinstance(h, Handler):
+                h.handle_timer()
+
 def parse_command(command):
     r"""parse_command(command) -> (args, kwargs) :: Parse a command.
 
index 06922a72fe9290bcfb5133700e49ae6187d0dc3c..b6be5a1003d891b7e857a166b9523647de3a3005 100644 (file)
@@ -7,6 +7,8 @@ Please see EventLoop class documentation for more info.
 """
 
 import select
+import errno
+import signal
 from select import POLLIN, POLLPRI, POLLERR
 
 __ALL__ = ('EventLoop', 'LoopInterruptedError')
@@ -35,8 +37,16 @@ class LoopInterruptedError(RuntimeError):
         r"str(obj) -> String representation."
         return 'Loop interrupted: %s' % self.select_error
 
+# Flag to know if a timer was expired
+timeout = False
+
+# Alarm Signal handler
+def alarm_handler(signum, stack_frame):
+    global timeout
+    timeout = True
+
 class EventLoop:
-    r"""EventLoop(file[, handler]) -> EventLoop instance
+    r"""EventLoop(file[, timer[, handler[, timer_handler]]]) -> EventLoop.
 
     This class implements a simple event loop based on select module.
     It "listens" to activity a single 'file' object (a file, a pipe,
@@ -44,9 +54,12 @@ class EventLoop:
     function (or the handle() method if you prefer subclassing) every
     time the file is ready for reading (or has an error).
 
+    If a 'timer' is supplied, then the timer_handler() function object
+    (or the handle_timer() method) is called every 'timer' seconds.
+
     This is a really simple example of usage using a hanlder callable:
 
-    >>> import os 
+    >>> import os
     >>> def handle(event_loop):
             data = os.read(event_loop.fileno, 100)
             os.write(1, 'Received message: %r\n' % data)
@@ -66,14 +79,16 @@ class EventLoop:
     >>>             self.stop()
     >>>         else:
     >>>             os.write(1, 'Received message: %r\n' % data)
-    >>> p = Test(0)
+    >>>     def handle_timer(self):
+    >>>         print time.strftime('%c')
+    >>> p = Test(0, timer=5)
     >>> p.loop()
 
     This example loops until the user enters a single "q", when stop()
     is called and the event loop is exited.
     """
 
-    def __init__(self, file, handler=None):
+    def __init__(self, file, handler=None, timer=None, timer_handler=None):
         r"""Initialize the EventLoop object.
 
         See EventLoop class documentation for more info.
@@ -81,7 +96,9 @@ class EventLoop:
         self.poll = select.poll()
         self._stop = False
         self.__register(file)
+        self.timer = timer
         self.handler = handler
+        self.timer_handler = timer_handler
 
     def __register(self, file):
         r"__register(file) -> None :: Register a new file for polling."
@@ -125,26 +142,44 @@ class EventLoop:
         Wait for events and handle then when they arrive. If once is True,
         then only 1 event is processed and then this method returns.
         """
+        # Flag modified by the signal handler
+        global timeout
+        # If we use a timer, we set up the signal
+        if self.timer is not None:
+            signal.signal(signal.SIGALRM, alarm_handler)
+            signal.alarm(self.timer)
         while True:
             try:
                 res = self.poll.poll()
             except select.error, e:
-                raise LoopInterruptedError(e)
-            if self.handler is not None:
-                self.handler(self)
+                # The error is not an interrupt caused by the alarm, then raise
+                if e.args[0] != errno.EINTR or not timeout:
+                    raise LoopInterruptedError(e)
+            # There was a timeout, so execute the timer handler
+            if timeout:
+                timeout = False
+                self.handle_timer()
+                signal.alarm(self.timer)
+            # Not a timeout, execute the regular handler
             else:
                 self.handle()
+            # Look if we have to stop
             if self._stop or once:
                 self._stop = False
                 break
 
     def handle(self):
         r"handle() -> None :: Abstract method to be overriden to handle events."
-        raise NotImplementedError
+        self.handler(self)
+
+    def handle_timer(self):
+        r"handle() -> None :: Abstract method to be overriden to handle events."
+        self.timer_handler(self)
 
 if __name__ == '__main__':
 
     import os
+    import time
 
     def handle(event_loop):
         data = os.read(event_loop.fileno, 100)
@@ -163,8 +198,10 @@ if __name__ == '__main__':
                 self.stop()
             else:
                 os.write(1, 'Received message: %r\n' % data)
+        def handle_timer(self):
+            print time.strftime('%c')
 
-    p = Test(0)
+    p = Test(0, timer=5)
 
     os.write(1, 'Say a lot of things, then press write just "q" to stop: ')
     p.loop()
index f7497536835ae72670cc2ca3acbcf55ba5d58ba3..8ed3f1576b93b2db18fd793e5e4b2e99fc1615e9 100644 (file)
@@ -37,7 +37,7 @@ class PyminDaemon(eventloop.EventLoop):
     >>> PyminDaemon(Root(), ('', 9999)).run()
     """
 
-    def __init__(self, root, bind_addr=('', 9999)):
+    def __init__(self, root, bind_addr=('', 9999), timer=1):
         r"""Initialize the PyminDaemon object.
 
         See PyminDaemon class documentation for more info.
@@ -47,7 +47,7 @@ class PyminDaemon(eventloop.EventLoop):
         sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
         sock.bind(bind_addr)
         # Create EventLoop
-        eventloop.EventLoop.__init__(self, sock)
+        eventloop.EventLoop.__init__(self, sock, timer=timer)
         # Create Dispatcher
         #TODO root.pymin = PyminHandler()
         self.dispatcher = dispatcher.Dispatcher(root)
@@ -84,6 +84,10 @@ class PyminDaemon(eventloop.EventLoop):
             response += u'%d\n%s' % (len(result), result)
         self.file.sendto(response.encode('utf-8'), addr)
 
+    def handle_timer(self):
+        r"handle_timer() -> None :: Call handle_timer() on handlers."
+        self.dispatcher.root.handle_timer()
+
     def run(self):
         r"run() -> None :: Run the event loop (shortcut to loop())"
         try: