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

@ -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)