Made objects auto-unsubscribe from tickerhandler when deleted (resolves #515). Fixed a bug that made typeclass loading mechanism not report errors as verbosely as it could.

This commit is contained in:
Griatch 2014-06-14 19:31:19 +02:00
parent ce2d11ad12
commit 57144b2c21
3 changed files with 48 additions and 17 deletions

View file

@ -80,6 +80,10 @@ class Ticker(object):
""" """
for key, (obj, args, kwargs) in self.subscriptions.items(): for key, (obj, args, kwargs) in self.subscriptions.items():
hook_key = yield kwargs.get("hook_key", "at_tick") hook_key = yield kwargs.get("hook_key", "at_tick")
if not obj:
# object was deleted between calls
self.validate()
continue
try: try:
yield _GA(obj, hook_key)(*args, **kwargs) yield _GA(obj, hook_key)(*args, **kwargs)
except Exception: except Exception:
@ -108,7 +112,7 @@ class Ticker(object):
if not subs: if not subs:
self.task.stop() self.task.stop()
elif subs: elif subs:
print "starting with start_delay=", start_delay #print "starting with start_delay=", start_delay
self.task.start(self.interval, now=False, start_delay=start_delay) self.task.start(self.interval, now=False, start_delay=start_delay)
def add(self, store_key, obj, *args, **kwargs): def add(self, store_key, obj, *args, **kwargs):
@ -174,6 +178,7 @@ class TickerPool(object):
for ticker in self.tickers.values(): for ticker in self.tickers.values():
ticker.stop() ticker.stop()
class TickerHandler(object): class TickerHandler(object):
""" """
The Tickerhandler maintains a pool of tasks for subscribing The Tickerhandler maintains a pool of tasks for subscribing
@ -266,15 +271,31 @@ class TickerHandler(object):
self.save() self.save()
self.ticker_pool.add(store_key, obj, interval, *args, **kwargs) self.ticker_pool.add(store_key, obj, interval, *args, **kwargs)
def remove(self, obj, interval): def remove(self, obj, interval=None):
""" """
Remove object from ticker with given interval. Remove object from ticker, or only this object ticking
at a given interval.
""" """
isdb, store_key = self._store_key(obj, interval) if interval:
if isdb: isdb, store_key = self._store_key(obj, interval)
self.ticker_storage.pop(store_key, None) if isdb:
self.save() self.ticker_storage.pop(store_key, None)
self.ticker_pool.remove(store_key, interval) self.save()
self.ticker_pool.remove(store_key, interval)
else:
# remove all objects with any intervals
intervals = self.ticker_pool.tickers.keys()
should_save = False
for interval in intervals:
isdb, store_key = self._store_key(obj, interval)
if isdb:
self.ticker_storage.pop(store_key, None)
should_save = True
self.ticker_pool.remove(store_key, interval)
if should_save:
self.save()
def clear(self, interval=None): def clear(self, interval=None):
""" """
@ -306,5 +327,6 @@ class TickerHandler(object):
if ticker: if ticker:
return ticker.subscriptions.values() return ticker.subscriptions.values()
# main tickerhandler # main tickerhandler
TICKER_HANDLER = TickerHandler() TICKER_HANDLER = TickerHandler()

View file

@ -51,6 +51,8 @@ from src.utils.picklefield import PickledObjectField
__all__ = ("Attribute", "TypeNick", "TypedObject") __all__ = ("Attribute", "TypeNick", "TypedObject")
TICKER_HANDLER = None
_PERMISSION_HIERARCHY = [p.lower() for p in settings.PERMISSION_HIERARCHY] _PERMISSION_HIERARCHY = [p.lower() for p in settings.PERMISSION_HIERARCHY]
_TYPECLASS_AGGRESSIVE_CACHE = settings.TYPECLASS_AGGRESSIVE_CACHE _TYPECLASS_AGGRESSIVE_CACHE = settings.TYPECLASS_AGGRESSIVE_CACHE
@ -841,6 +843,11 @@ class TypedObject(SharedMemoryModel):
raise Exception("dbref cannot be deleted!") raise Exception("dbref cannot be deleted!")
dbref = property(__dbref_get, __dbref_set, __dbref_del) dbref = property(__dbref_get, __dbref_set, __dbref_del)
# the latest error string will be stored here for accessing methods to access.
# It is set by _display_errmsg, which will print to log if error happens
# during server startup.
typeclass_last_errmsg = ""
# typeclass property # typeclass property
#@property #@property
def __typeclass_get(self): def __typeclass_get(self):
@ -855,7 +862,6 @@ class TypedObject(SharedMemoryModel):
of normal dot notation) is due to optimization: it avoids calling of normal dot notation) is due to optimization: it avoids calling
the custom self.__getattribute__ more than necessary. the custom self.__getattribute__ more than necessary.
""" """
path = _GA(self, "typeclass_path") path = _GA(self, "typeclass_path")
typeclass = _GA(self, "_cached_typeclass") typeclass = _GA(self, "_cached_typeclass")
try: try:
@ -898,7 +904,10 @@ class TypedObject(SharedMemoryModel):
errstring += " to specify the actual typeclass name inside the module too." errstring += " to specify the actual typeclass name inside the module too."
elif typeclass: elif typeclass:
errstring += "\n%s" % typeclass.strip() # this will hold a growing error message. errstring += "\n%s" % typeclass.strip() # this will hold a growing error message.
errstring += "\nTypeclass failed to load. Falling back to default." if not errstring:
errstring = "\nMake sure the path is set correctly. Paths tested:\n"
errstring += ", ".join(typeclass_paths)
errstring += "\nTypeclass code was not found or failed to load."
# If we reach this point we couldn't import any typeclasses. Return # If we reach this point we couldn't import any typeclasses. Return
# default. It's up to the calling method to use e.g. self.is_typeclass() # default. It's up to the calling method to use e.g. self.is_typeclass()
# to detect that the result is not the one asked for. # to detect that the result is not the one asked for.
@ -913,10 +922,6 @@ class TypedObject(SharedMemoryModel):
# typeclass property # typeclass property
typeclass = property(__typeclass_get, fdel=__typeclass_del) typeclass = property(__typeclass_get, fdel=__typeclass_del)
# the last error string will be stored here for accessing methods to access.
# It is set by _display_errmsg, which will print to log if error happens
# during server startup.
typeclass_last_errmsg = ""
def _path_import(self, path): def _path_import(self, path):
""" """
@ -938,7 +943,7 @@ class TypedObject(SharedMemoryModel):
# we separate between not finding the module, and finding # we separate between not finding the module, and finding
# a buggy one. # a buggy one.
pass pass
#errstring = ""#Typeclass not found trying path '%s'." % path #errstring = "Typeclass not found trying path '%s'." % path
else: else:
# a bug in the module is reported normally. # a bug in the module is reported normally.
trc = traceback.format_exc().strip() trc = traceback.format_exc().strip()
@ -958,7 +963,7 @@ class TypedObject(SharedMemoryModel):
""" """
Helper function to display error. Helper function to display error.
""" """
_SA(self, "typeclass_lasterrmsg", message) _SA(self, "typeclass_last_errmsg", message)
if ServerConfig.objects.conf("server_starting_mode"): if ServerConfig.objects.conf("server_starting_mode"):
print message print message
else: else:
@ -1180,6 +1185,10 @@ class TypedObject(SharedMemoryModel):
def delete(self): def delete(self):
"Cleaning up handlers on the typeclass level" "Cleaning up handlers on the typeclass level"
global TICKER_HANDLER
if not TICKER_HANDLER:
from src.scripts.tickerhandler import TICKER_HANDLER
TICKER_HANDLER.remove(self) # removes all ticker subscriptions
_GA(self, "permissions").clear() _GA(self, "permissions").clear()
_SA(self, "_cached_typeclass", None) _SA(self, "_cached_typeclass", None)
_GA(self, "flush_from_cache")() _GA(self, "flush_from_cache")()

View file

@ -145,7 +145,7 @@ def create_object(typeclass=None, key=None, location=None,
# gave us a default # gave us a default
SharedMemoryModel.delete(new_db_object) SharedMemoryModel.delete(new_db_object)
if report_to: if report_to:
_GA(report_to, "msg")("Error creating %s (%s):\n%s" % (new_db_object.key, typeclass, _GA(report_to, "msg")("Error creating %s (%s).\n%s" % (new_db_object.key, typeclass,
_GA(new_db_object, "typeclass_last_errmsg"))) _GA(new_db_object, "typeclass_last_errmsg")))
return None return None
else: else: