From 57144b2c211f4892febb45b230d684296a0ec244 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 14 Jun 2014 19:31:19 +0200 Subject: [PATCH] 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. --- src/scripts/tickerhandler.py | 38 ++++++++++++++++++++++++++++-------- src/typeclasses/models.py | 25 ++++++++++++++++-------- src/utils/create.py | 2 +- 3 files changed, 48 insertions(+), 17 deletions(-) diff --git a/src/scripts/tickerhandler.py b/src/scripts/tickerhandler.py index a00997934a..9dd7c33e56 100644 --- a/src/scripts/tickerhandler.py +++ b/src/scripts/tickerhandler.py @@ -80,6 +80,10 @@ class Ticker(object): """ for key, (obj, args, kwargs) in self.subscriptions.items(): hook_key = yield kwargs.get("hook_key", "at_tick") + if not obj: + # object was deleted between calls + self.validate() + continue try: yield _GA(obj, hook_key)(*args, **kwargs) except Exception: @@ -108,7 +112,7 @@ class Ticker(object): if not subs: self.task.stop() 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) def add(self, store_key, obj, *args, **kwargs): @@ -174,6 +178,7 @@ class TickerPool(object): for ticker in self.tickers.values(): ticker.stop() + class TickerHandler(object): """ The Tickerhandler maintains a pool of tasks for subscribing @@ -266,15 +271,31 @@ class TickerHandler(object): self.save() 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 isdb: - self.ticker_storage.pop(store_key, None) - self.save() - self.ticker_pool.remove(store_key, interval) + if interval: + isdb, store_key = self._store_key(obj, interval) + if isdb: + self.ticker_storage.pop(store_key, None) + 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): """ @@ -306,5 +327,6 @@ class TickerHandler(object): if ticker: return ticker.subscriptions.values() + # main tickerhandler TICKER_HANDLER = TickerHandler() diff --git a/src/typeclasses/models.py b/src/typeclasses/models.py index 484b0cf2af..26ea351bc9 100644 --- a/src/typeclasses/models.py +++ b/src/typeclasses/models.py @@ -51,6 +51,8 @@ from src.utils.picklefield import PickledObjectField __all__ = ("Attribute", "TypeNick", "TypedObject") +TICKER_HANDLER = None + _PERMISSION_HIERARCHY = [p.lower() for p in settings.PERMISSION_HIERARCHY] _TYPECLASS_AGGRESSIVE_CACHE = settings.TYPECLASS_AGGRESSIVE_CACHE @@ -841,6 +843,11 @@ class TypedObject(SharedMemoryModel): raise Exception("dbref cannot be deleted!") 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 #@property def __typeclass_get(self): @@ -855,7 +862,6 @@ class TypedObject(SharedMemoryModel): of normal dot notation) is due to optimization: it avoids calling the custom self.__getattribute__ more than necessary. """ - path = _GA(self, "typeclass_path") typeclass = _GA(self, "_cached_typeclass") try: @@ -898,7 +904,10 @@ class TypedObject(SharedMemoryModel): errstring += " to specify the actual typeclass name inside the module too." elif typeclass: 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 # 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. @@ -913,10 +922,6 @@ class TypedObject(SharedMemoryModel): # typeclass property 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): """ @@ -938,7 +943,7 @@ class TypedObject(SharedMemoryModel): # we separate between not finding the module, and finding # a buggy one. pass - #errstring = ""#Typeclass not found trying path '%s'." % path + #errstring = "Typeclass not found trying path '%s'." % path else: # a bug in the module is reported normally. trc = traceback.format_exc().strip() @@ -958,7 +963,7 @@ class TypedObject(SharedMemoryModel): """ Helper function to display error. """ - _SA(self, "typeclass_lasterrmsg", message) + _SA(self, "typeclass_last_errmsg", message) if ServerConfig.objects.conf("server_starting_mode"): print message else: @@ -1180,6 +1185,10 @@ class TypedObject(SharedMemoryModel): def delete(self): "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() _SA(self, "_cached_typeclass", None) _GA(self, "flush_from_cache")() diff --git a/src/utils/create.py b/src/utils/create.py index de90d37c8d..cfcf8a903a 100644 --- a/src/utils/create.py +++ b/src/utils/create.py @@ -145,7 +145,7 @@ def create_object(typeclass=None, key=None, location=None, # gave us a default SharedMemoryModel.delete(new_db_object) 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"))) return None else: