From: Leandro Lucarella Date: Wed, 24 Oct 2007 20:41:28 +0000 (-0300) Subject: Add support for a simple timer to do periodic tasks. X-Git-Url: https://git.llucax.com/software/pymin.git/commitdiff_plain/e54e65428d8e47408a77e16a198bb0fbe9afc4cb?ds=inline Add support for a simple timer to do periodic tasks. 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. --- diff --git a/pymin/dispatcher.py b/pymin/dispatcher.py index 17075a3..24f0d1f 100644 --- a/pymin/dispatcher.py +++ b/pymin/dispatcher.py @@ -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. diff --git a/pymin/eventloop.py b/pymin/eventloop.py index 06922a7..b6be5a1 100644 --- a/pymin/eventloop.py +++ b/pymin/eventloop.py @@ -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() diff --git a/pymin/pymindaemon.py b/pymin/pymindaemon.py index f749753..8ed3f15 100644 --- a/pymin/pymindaemon.py +++ b/pymin/pymindaemon.py @@ -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: