Resolves problems in storing/restoring tickerhandler keys. Resolves #997.

This commit is contained in:
Griatch 2016-06-24 18:14:11 +02:00
parent b014df00de
commit 03ef093d36
2 changed files with 37 additions and 26 deletions

View file

@ -352,20 +352,21 @@ class TickerHandler(object):
shutdown or not. shutdown or not.
Returns: Returns:
isdb_and_store_key (tuple): A tuple `(obj, path, interval, store_key (tuple): A tuple `(packed_obj, methodname, outpath, interval,
methodname, idstring)` that uniquely identifies the idstring, persistent)` that uniquely identifies the
ticker. `path` is `None` and `methodname` is the name of ticker. Here, `packed_obj` is the unique string representation of the
the method if `obj_or_path` is a database object. object or `None`. The `methodname` is the string name of the method on
Vice-versa, `obj` and `methodname` are `None` if `packed_obj` to call, or `None` if `packed_obj` is unset. `path` is
`obj_or_path` is a python-path. the Python-path to a non-method callable, or `None`. Finally, `interval`
`idstring` and `persistent` are integers, strings and bools respectively.
""" """
interval = int(interval) interval = int(interval)
persistent = bool(persistent) persistent = bool(persistent)
outobj = pack_dbobj(obj) packed_obj = pack_dbobj(obj)
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) outpath = path if path and isinstance(path, basestring) else None
return (packed_obj, methodname, outpath, interval, idstring, persistent)
def save(self): def save(self):
""" """
@ -382,14 +383,15 @@ class TickerHandler(object):
# remove any subscriptions that lost its object in the interim # 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() 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)} if ((store_key[1] and ("_obj" in kwargs and kwargs["_obj"].pk) and
hasattr(kwargs["_obj"], store_key[1])) or # a valid method with existing obj
store_key[2])} # a path given
# update the timers for the tickers # update the timers for the tickers
for store_key, (args, kwargs) in to_save.items(): for store_key, (args, kwargs) in to_save.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)
kwargs.pop("_obj", None)
ServerConfig.objects.conf(key=self.save_name, value=dbserialize(to_save)) 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
@ -413,22 +415,22 @@ class TickerHandler(object):
# the dbunserialize will convert all serialized dbobjs to real objects # the dbunserialize will convert all serialized dbobjs to real objects
restored_tickers = dbunserialize(restored_tickers) restored_tickers = dbunserialize(restored_tickers)
ticker_storage = {} self.ticker_storage = {}
for store_key, (args, kwargs) in restored_tickers.iteritems(): for store_key, (args, kwargs) in restored_tickers.iteritems():
try: try:
# at this point obj is the actual object (or None) due to how
# the dbunserialize works
obj, callfunc, 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 inspect.ismethod(callfunc) and not obj: if isinstance(callfunc, basestring) and not obj:
# methods must have an existing object
continue continue
if obj: # we must rebuild the store_key here since obj must not be
try: # stored as the object itself for the store_key to be hashable.
obj = unpack_dbobj(obj) store_key = self._store_key(obj, path, interval, callfunc, idstring, persistent)
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: if obj and callfunc:
kwargs["_callback"] = callfunc kwargs["_callback"] = callfunc
kwargs["_obj"] = obj kwargs["_obj"] = obj
@ -437,12 +439,16 @@ class TickerHandler(object):
callback = variable_from_module(modname, varname) callback = variable_from_module(modname, varname)
kwargs["_callback"] = callback kwargs["_callback"] = callback
kwargs["_obj"] = None kwargs["_obj"] = None
ticker_storage[store_key] = (args, kwargs) else:
except Exception as err: # Neither object nor path - discard this ticker
log_err("Tickerhandler: Removing malformed ticker: %s" % str(store_key))
continue
except Exception:
# this suggests a malformed save or missing objects # this suggests a malformed save or missing objects
log_err("%s\nTickerhandler: Removing malformed ticker: %s" % (err, str(store_key))) log_trace("Tickerhandler: Removing malformed ticker: %s" % str(store_key))
continue continue
self.ticker_storage = ticker_storage # if we get here we should create a new ticker
self.ticker_storage[store_key] = (args, kwargs)
self.ticker_pool.add(store_key, *args, **kwargs) self.ticker_pool.add(store_key, *args, **kwargs)
def add(self, interval=60, callback=None, idstring="", persistent=True, *args, **kwargs): def add(self, interval=60, callback=None, idstring="", persistent=True, *args, **kwargs):
@ -481,11 +487,11 @@ class TickerHandler(object):
obj, path, callfunc = self._get_callback(callback) obj, path, callfunc = self._get_callback(callback)
store_key = self._store_key(obj, path, interval, callfunc, idstring, persistent) store_key = self._store_key(obj, path, interval, callfunc, idstring, persistent)
self.ticker_storage[store_key] = (args, kwargs)
self.save()
kwargs["_obj"] = obj kwargs["_obj"] = obj
kwargs["_callback"] = callfunc # either method-name or callable kwargs["_callback"] = callfunc # either method-name or callable
self.ticker_storage[store_key] = (args, kwargs)
self.ticker_pool.add(store_key, *args, **kwargs) self.ticker_pool.add(store_key, *args, **kwargs)
self.save()
def remove(self, interval=60, callback=None, idstring="", persistent=True): def remove(self, interval=60, callback=None, idstring="", persistent=True):
""" """

View file

@ -278,6 +278,11 @@ def unpack_dbobj(item):
obj = item[3] and _TO_MODEL_MAP[item[1]].objects.get(id=item[3]) obj = item[3] and _TO_MODEL_MAP[item[1]].objects.get(id=item[3])
except ObjectDoesNotExist: except ObjectDoesNotExist:
return None return None
except TypeError:
if hasattr(item, "pk"):
# this happens if item is already an obj
return item
return None
# even if we got back a match, check the sanity of the date (some # even if we got back a match, check the sanity of the date (some
# databases may 're-use' the id) # databases may 're-use' the id)
return _TO_DATESTRING(obj) == item[2] and obj or None return _TO_DATESTRING(obj) == item[2] and obj or None