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:
parent
ce2d11ad12
commit
57144b2c21
3 changed files with 48 additions and 17 deletions
|
|
@ -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.
|
||||||
"""
|
"""
|
||||||
|
if interval:
|
||||||
isdb, store_key = self._store_key(obj, interval)
|
isdb, store_key = self._store_key(obj, interval)
|
||||||
if isdb:
|
if isdb:
|
||||||
self.ticker_storage.pop(store_key, None)
|
self.ticker_storage.pop(store_key, None)
|
||||||
self.save()
|
self.save()
|
||||||
self.ticker_pool.remove(store_key, interval)
|
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()
|
||||||
|
|
|
||||||
|
|
@ -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")()
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue