From ee269d13cf1b628360b77d9656dfed7fa0c809d8 Mon Sep 17 00:00:00 2001 From: Griatch Date: Wed, 12 Feb 2014 00:38:56 +0100 Subject: [PATCH] Cleaned up the tickerhandler --- src/scripts/tickerhandler.py | 88 ++++++++++++++++++++++++++++++------ src/server/oobhandler.py | 24 ++++++---- 2 files changed, 87 insertions(+), 25 deletions(-) diff --git a/src/scripts/tickerhandler.py b/src/scripts/tickerhandler.py index 5792c17561..ab1c41e5ea 100644 --- a/src/scripts/tickerhandler.py +++ b/src/scripts/tickerhandler.py @@ -1,11 +1,54 @@ """ -Tickerhandler +TickerHandler This implements an efficient Ticker which uses a subscription -model to 'tick' subscribed objects at regular intervals. All -that is required is that the subscribing objects has a -method "at_tick". +model to 'tick' subscribed objects at regular intervals. + +The ticker mechanism is used by importing and accessing +the instantiated TICKER_HANDLER instance in this module. This +instance is run by the server; it will save its status across +server reloads and be started automaticall on boot. + +Example: + + from src.scripts.tickerhandler import TICKER_HANDLER + + # tick myobj every 15 seconds + TICKER_HANDLER.add(myobj, 15) + +The handler will by default try to call a hook "at_tick()" +on the subscribing object. The hook's name can be changed +if the "hook_key" keyword is given to the add() method (only +one such alternate name per interval though). The +handler will transparently set up and add new timers behind +the scenes to tick at given intervals, using a TickerPool. + +To remove: + + TICKER_HANDLER.remove(myobj, 15) + +The interval must be given since a single object can be subcribed +to many different tickers at the same time. + + +The TickerHandler's functionality can be overloaded by modifying the +Ticker class and then changing TickerPool and TickerHandler to use the +custom classes + +class MyTicker(Ticker): + # [doing custom stuff] + +class MyTickerPool(TickerPool): + ticker_class = MyTicker +class MyTickerHandler(TickerHandler): + ticker_pool_class = MyTickerPool + +If one wants to duplicate TICKER_HANDLER's auto-saving feature in +a custom handler one can make a custom AT_STARTSTOP_MODULE entry to +call the handler's save() and restore() methods when the server reboots. + """ +from twisted.internet.defer import inlineCallbacks from twisted.internet.task import LoopingCall from src.server.models import ServerConfig from src.utils.logger import log_trace @@ -18,24 +61,38 @@ _SA = object.__setattr__ class Ticker(object): """ Represents a repeatedly running task that calls - hooks repeatedly. + hooks repeatedly. Overload _callback to change the + way it operates. """ + + @inlineCallbacks + def _callback(self): + """ + This will be called repeatedly every self.interval seconds. + self.subscriptions contain tuples of (obj, args, kwargs) for + each subscribing object. + + If overloading, this callback is expected to handle all + subscriptions when it is triggered. It should not return + anything and should not traceback on poorly designed hooks. + The callback should ideally work under @inlineCallbacks so it can yield + appropriately. + """ + for key, (obj, args, kwargs) in self.subscriptions.items(): + hook_key = yield kwargs.get("hook_key", "at_tick") + try: + yield _GA(obj, hook_key)(*args, **kwargs) + except Exception: + log_trace() + def __init__(self, interval): """ Set up the ticker """ - def callback(self): - "This should be fed _Task as argument" - for key, (obj, args, kwargs) in self.subscriptions.items(): - hook_key = kwargs.get("hook_key", "at_tick") - try: - _GA(obj, hook_key)(*args, **kwargs) - except Exception: - log_trace() - self.interval = interval self.subscriptions = {} - self.task = LoopingCall(callback, self) + # set up a twisted asynchronous repeat call + self.task = LoopingCall(self._callback) def validate(self): """ @@ -74,6 +131,7 @@ class Ticker(object): self.subscriptions = {} self.validate() + class TickerPool(object): """ This maintains a pool of Twisted LoopingCall tasks diff --git a/src/server/oobhandler.py b/src/server/oobhandler.py index bf33e06e06..5b282895f4 100644 --- a/src/server/oobhandler.py +++ b/src/server/oobhandler.py @@ -30,6 +30,8 @@ a global function oob_error will be used as optional error management. """ from inspect import isfunction +from twisted.internet.defer import inlineCallbacks +from twisted.internet.task import LoopingCall from django.conf import settings from src.server.models import ServerConfig from src.server.sessionhandler import SESSIONS @@ -203,25 +205,27 @@ class TrackerBase(object): # for script in self.scripts.values(): # script.stop() -from twisted.internet.task import LoopingCall class OOBTicker(Ticker): """ Version of Ticker that calls OOB_FUNC rather than trying to call a hook method. """ - def __init__(self, interval): - def callback(self, oobhandler, sessions): - for key, (_, args, kwargs) in self.subscriptions.items(): - session = sessions.session_from_sessid(kwargs.get("sessid")) - try: - oobhandler.execute_cmd(session, kwargs.get("func_key"), *args, **kwargs) - except Exception: - logger.log_trace() + @inlineCallbacks + def _callback(self, oobhandler, sessions): + "See original for more info" + for key, (_, args, kwargs) in self.subscriptions.items(): + session = sessions.session_from_sessid(kwargs.get("sessid")) + try: + oobhandler.execute_cmd(session, kwargs.get("func_key"), *args, **kwargs) + except Exception: + logger.log_trace() + def __init__(self, interval): + "Sets up the Ticker" self.interval = interval self.subscriptions = {} - self.task = LoopingCall(callback, self, OOB_HANDLER, SESSIONS) + self.task = LoopingCall(self._callback, OOB_HANDLER, SESSIONS) class OOBTickerPool(TickerPool): ticker_class = OOBTicker