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.
This commit is contained in:
Griatch 2016-06-14 22:03:44 +02:00
parent 25e1126809
commit bdcc093c23
2 changed files with 37 additions and 17 deletions

View file

@ -18,11 +18,11 @@ Example:
TICKER_HANDLER.add(15, myobj.at_tick, *args, **kwargs) TICKER_HANDLER.add(15, myobj.at_tick, *args, **kwargs)
``` ```
You supply the interval to tick and a callable to call regularly You supply the interval to tick and a callable to call regularly
with any extra args/kwargs. The handler will transparently set with any extra args/kwargs. The handler will transparently set
up and add new timers behind the scenes to tick at given intervals, up and add new timers behind the scenes to tick at given intervals,
using a TickerPool - all callables with the same interval will share using a TickerPool - all callables with the same interval will share
the interval ticker. the interval ticker.
To remove: 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. 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 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 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 The TickerHandler's functionality can be overloaded by modifying the
Ticker class and then changing TickerPool and TickerHandler to use 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.scripts.scripts import ExtendedLoopingCall
from evennia.server.models import ServerConfig from evennia.server.models import ServerConfig
from evennia.utils.logger import log_trace, log_err 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 from evennia.utils import variable_from_module
_GA = object.__getattribute__ _GA = object.__getattribute__
@ -339,7 +339,8 @@ class TickerHandler(object):
Tries to create a store_key for the object. Tries to create a store_key for the object.
Args: 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. path (str or None): Python-path to callable, if any.
interval (int): Ticker interval. interval (int): Ticker interval.
callfunc (callable or str): This is either the callable function or callfunc (callable or str): This is either the callable function or
@ -361,7 +362,7 @@ class TickerHandler(object):
""" """
interval = int(interval) interval = int(interval)
persistent = bool(persistent) 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 outpath = path if path and isinstance(path, basestring) else None
methodname = callfunc if callfunc and isinstance(callfunc, basestring) else None methodname = callfunc if callfunc and isinstance(callfunc, basestring) else None
return (outobj, methodname, outpath, interval, idstring, persistent) return (outobj, methodname, outpath, interval, idstring, persistent)
@ -375,16 +376,21 @@ class TickerHandler(object):
""" """
if self.ticker_storage: 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()) start_delays = dict((interval, ticker.task.next_call_time())
for interval, ticker in self.ticker_pool.tickers.items()) 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 # update the timers for the tickers
#for (obj, interval, idstring), (args, kwargs) in self.ticker_storage.items(): for store_key, (args, kwargs) in to_save.items():
for store_key, (args, kwargs) in self.ticker_storage.items():
interval = store_key[1] interval = store_key[1]
# this is a mutable, so it's updated in-place in ticker_storage # this is a mutable, so it's updated in-place in ticker_storage
kwargs["_start_delay"] = start_delays.get(interval, None) kwargs["_start_delay"] = start_delays.get(interval, None)
ServerConfig.objects.conf(key=self.save_name, kwargs.pop("_obj", None)
value=dbserialize(self.ticker_storage)) ServerConfig.objects.conf(key=self.save_name, value=dbserialize(to_save))
else: else:
# make sure we have nothing lingering in the database # make sure we have nothing lingering in the database
ServerConfig.objects.conf(key=self.save_name, delete=True) ServerConfig.objects.conf(key=self.save_name, delete=True)
@ -410,12 +416,21 @@ class TickerHandler(object):
ticker_storage = {} ticker_storage = {}
for store_key, (args, kwargs) in restored_tickers.iteritems(): for store_key, (args, kwargs) in restored_tickers.iteritems():
try: try:
obj, methodname, path, interval, idstring, persistent = store_key obj, callfunc, path, interval, idstring, persistent = store_key
if not persistent and not server_reload: if not persistent and not server_reload:
# this ticker will not be restarted # this ticker will not be restarted
continue continue
if obj and methodname: if inspect.ismethod(callfunc) and not obj:
kwargs["_callback"] = methodname 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 kwargs["_obj"] = obj
elif path: elif path:
modname, varname = path.rsplit(".", 1) modname, varname = path.rsplit(".", 1)
@ -550,7 +565,8 @@ class TickerHandler(object):
""" """
store_keys = [] store_keys = []
for ticker in self.ticker_pool.tickers.itervalues(): 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 return store_keys
# main tickerhandler # main tickerhandler

View file

@ -83,7 +83,11 @@ def _TO_DATESTRING(obj):
return _GA(obj, "db_date_created").strftime(_DATESTRING) return _GA(obj, "db_date_created").strftime(_DATESTRING)
except AttributeError: except AttributeError:
# this can happen if object is not yet saved - no datestring is then set # 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) return _GA(obj, "db_date_created").strftime(_DATESTRING)