From bdcc093c239b2df5b3bf331191a104b355b9e156 Mon Sep 17 00:00:00 2001 From: Griatch Date: Tue, 14 Jun 2016 22:03:44 +0200 Subject: [PATCH] Make tickerhandler correctly clean subs to deleted objs. Also makes the dbunserialize mechanism a little more robust, making it return None instead of crashing when unpacking an invalid packed-tuple. --- evennia/scripts/tickerhandler.py | 48 +++++++++++++++++++++----------- evennia/utils/dbserialize.py | 6 +++- 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/evennia/scripts/tickerhandler.py b/evennia/scripts/tickerhandler.py index 668d8e7d96..161b2e2fec 100644 --- a/evennia/scripts/tickerhandler.py +++ b/evennia/scripts/tickerhandler.py @@ -18,11 +18,11 @@ Example: TICKER_HANDLER.add(15, myobj.at_tick, *args, **kwargs) ``` -You supply the interval to tick and a callable to call regularly -with any extra args/kwargs. The handler will transparently set -up and add new timers behind the scenes to tick at given intervals, +You supply the interval to tick and a callable to call regularly +with any extra args/kwargs. The handler will transparently set +up and add new timers behind the scenes to tick at given intervals, using a TickerPool - all callables with the same interval will share -the interval ticker. +the interval ticker. To remove: @@ -37,7 +37,7 @@ but with different arguments (args/kwargs are not used for identifying the ticke is also `persistent=False` if you don't want to make a ticker that don't survive a reload. If either or both `idstring` or `persistent` has been changed from their defaults, they must be supplied to the `TICKER_HANDLER.remove` call to properly identify the ticker -to remove. +to remove. The TickerHandler's functionality can be overloaded by modifying the Ticker class and then changing TickerPool and TickerHandler to use the @@ -66,7 +66,7 @@ from django.core.exceptions import ObjectDoesNotExist from evennia.scripts.scripts import ExtendedLoopingCall from evennia.server.models import ServerConfig from evennia.utils.logger import log_trace, log_err -from evennia.utils.dbserialize import dbserialize, dbunserialize +from evennia.utils.dbserialize import dbserialize, dbunserialize, pack_dbobj, unpack_dbobj from evennia.utils import variable_from_module _GA = object.__getattribute__ @@ -339,7 +339,8 @@ class TickerHandler(object): Tries to create a store_key for the object. Args: - obj (Object or None): Subscribing object if any. + obj (Object, tuple or None): Subscribing object if any. If a tuple, this is + a packed_obj tuple from dbserialize. path (str or None): Python-path to callable, if any. interval (int): Ticker interval. callfunc (callable or str): This is either the callable function or @@ -361,7 +362,7 @@ class TickerHandler(object): """ interval = int(interval) persistent = bool(persistent) - outobj = obj if obj and hasattr(obj, "db_key") else None + outobj = pack_dbobj(obj) outpath = path if path and isinstance(path, basestring) else None methodname = callfunc if callfunc and isinstance(callfunc, basestring) else None return (outobj, methodname, outpath, interval, idstring, persistent) @@ -375,16 +376,21 @@ class TickerHandler(object): """ if self.ticker_storage: + # get the current times so the tickers can be restarted with a delay later start_delays = dict((interval, ticker.task.next_call_time()) for interval, ticker in self.ticker_pool.tickers.items()) + + # remove any subscriptions that lost its object in the interim + to_save = {store_key: (args, kwargs) for store_key, (args, kwargs) in self.ticker_storage.items() + if inspect.ismethod(store_key[1]) and (not "_obj" in kwargs or kwargs["_obj"].pk)} + # update the timers for the tickers - #for (obj, interval, idstring), (args, kwargs) in self.ticker_storage.items(): - for store_key, (args, kwargs) in self.ticker_storage.items(): + for store_key, (args, kwargs) in to_save.items(): interval = store_key[1] # this is a mutable, so it's updated in-place in ticker_storage kwargs["_start_delay"] = start_delays.get(interval, None) - ServerConfig.objects.conf(key=self.save_name, - value=dbserialize(self.ticker_storage)) + kwargs.pop("_obj", None) + ServerConfig.objects.conf(key=self.save_name, value=dbserialize(to_save)) else: # make sure we have nothing lingering in the database ServerConfig.objects.conf(key=self.save_name, delete=True) @@ -410,12 +416,21 @@ class TickerHandler(object): ticker_storage = {} for store_key, (args, kwargs) in restored_tickers.iteritems(): try: - obj, methodname, path, interval, idstring, persistent = store_key + obj, callfunc, path, interval, idstring, persistent = store_key if not persistent and not server_reload: # this ticker will not be restarted continue - if obj and methodname: - kwargs["_callback"] = methodname + if inspect.ismethod(callfunc) and not obj: + continue + if obj: + try: + obj = unpack_dbobj(obj) + except IndexError: + # this happens with an old save, where obj was + # saved as itself; we must re-do the store_key. + store_key = self._store_key(obj, path, interval, callfunc, idstring, persistent) + if obj and callfunc: + kwargs["_callback"] = callfunc kwargs["_obj"] = obj elif path: modname, varname = path.rsplit(".", 1) @@ -550,7 +565,8 @@ class TickerHandler(object): """ store_keys = [] for ticker in self.ticker_pool.tickers.itervalues(): - store_keys.extend([store_key for store_key in ticker.subscriptions]) + for (objtup, callfunc, path, interval, idstring, persistent), (args, kwargs) in ticker.subscriptions.iteritems(): + store_keys.append((kwargs.get("_obj", None), callfunc, path, interval, idstring, persistent)) return store_keys # main tickerhandler diff --git a/evennia/utils/dbserialize.py b/evennia/utils/dbserialize.py index 923887c152..5f6f851de7 100644 --- a/evennia/utils/dbserialize.py +++ b/evennia/utils/dbserialize.py @@ -83,7 +83,11 @@ def _TO_DATESTRING(obj): return _GA(obj, "db_date_created").strftime(_DATESTRING) except AttributeError: # this can happen if object is not yet saved - no datestring is then set - obj.save() + try: + obj.save() + except AttributeError: + # we have received a None object, for example due to an erroneous save. + return None return _GA(obj, "db_date_created").strftime(_DATESTRING)