Added at_idmapper_flush to allow objects more control of their flush characteristics.

This commit is contained in:
Griatch 2016-10-02 19:14:22 +02:00
parent cd616caf29
commit 15025c0586
6 changed files with 74 additions and 33 deletions

View file

@ -51,10 +51,10 @@ class ContentsHandler(object):
Re-initialize the content cache Re-initialize the content cache
""" """
print "__init__", self.obj, calledby() #print "__init__", self.obj, calledby()
self._pkcache.update(dict((obj.pk, None) for obj in self._pkcache.update(dict((obj.pk, None) for obj in
ObjectDB.objects.filter(db_location=self.obj) if obj.pk)) ObjectDB.objects.filter(db_location=self.obj) if obj.pk))
print "init contentscache: self._pkcache:", self.obj, self._pkcache #print "init contentscache: self._pkcache:", self.obj, self._pkcache, id(self._pkcache), id(self)
def get(self, exclude=None): def get(self, exclude=None):
""" """
@ -70,15 +70,15 @@ class ContentsHandler(object):
if exclude: if exclude:
pks = [pk for pk in self._pkcache if pk not in [excl.pk for excl in make_iter(exclude)]] pks = [pk for pk in self._pkcache if pk not in [excl.pk for excl in make_iter(exclude)]]
else: else:
print calledby() #print calledby()
print "get: self._pkcache", self.obj, self._pkcache #print "get: self._pkcache", self.obj, self._pkcache, id(self._pkcache), id(self)
pks = self._pkcache pks = self._pkcache
try: try:
return [self._idcache[pk] for pk in pks] return [self._idcache[pk] for pk in pks]
except KeyError, err: except KeyError, err:
# this can happen if the idmapper cache was cleared for an object # this can happen if the idmapper cache was cleared for an object
# in the contents cache. If so we need to re-initialize and try again. # in the contents cache. If so we need to re-initialize and try again.
print "content_cache.get keyerror:", err, self._pkcache, self._idcache #print "content_cache.get keyerror:", err, self._pkcache, self._idcache
self.init() self.init()
try: try:
return [self._idcache[pk] for pk in pks] return [self._idcache[pk] for pk in pks]
@ -96,7 +96,7 @@ class ContentsHandler(object):
""" """
self._pkcache[obj.pk] = None self._pkcache[obj.pk] = None
print "add self._pkcache:", self.obj, obj, obj.pk, self._pkcache #print "add self._pkcache:", self.obj, obj, obj.pk, self._pkcache
def remove(self, obj): def remove(self, obj):
""" """
@ -107,7 +107,7 @@ class ContentsHandler(object):
""" """
self._pkcache.pop(obj.pk, None) self._pkcache.pop(obj.pk, None)
print "remove self._pkcache", self.obj, obj, obj.pk, self._pkcache #print "remove self._pkcache", self.obj, obj, obj.pk, self._pkcache
def clear(self): def clear(self):
""" """
@ -116,7 +116,7 @@ class ContentsHandler(object):
""" """
self._pkcache = {} self._pkcache = {}
self.init() self.init()
print "clear _pkcache", self.obj, self._pkcache #print "clear _pkcache", self.obj, self._pkcache
#------------------------------------------------------------ #------------------------------------------------------------
# #
@ -272,12 +272,12 @@ class ObjectDB(TypedObject):
# remove the safe flag # remove the safe flag
del self._safe_contents_update del self._safe_contents_update
print "location_set:", self.key, old_location, self.db_location #print "location_set:", self.key, old_location, self.db_location
# update the contents cache # update the contents cache
if old_location: if old_location:
old_location.contents_cache.remove(self) old_location.contents_cache.remove(self)
if self.db_location: if self.db_location:
print "cache add:", self.db_location, self #print "cache add:", self.db_location, self
self.db_location.contents_cache.add(self) self.db_location.contents_cache.add(self)
except RuntimeError: except RuntimeError:

View file

@ -228,7 +228,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
""" """
con = self.contents_cache.get(exclude=exclude) con = self.contents_cache.get(exclude=exclude)
print "contents_get:", self, con, calledby() #print "contents_get:", self, con, id(self), calledby()
return con return con
contents = property(contents_get) contents = property(contents_get)
@ -267,7 +267,6 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
return "{}(#{})".format(self.name, self.id) return "{}(#{})".format(self.name, self.id)
return self.name return self.name
def search(self, searchdata, def search(self, searchdata,
global_search=False, global_search=False,
use_nicks=True, # should this default to off? use_nicks=True, # should this default to off?
@ -1134,7 +1133,6 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
""" """
pass pass
# hooks called when moving the object # hooks called when moving the object
def at_before_move(self, destination): def at_before_move(self, destination):

View file

@ -325,6 +325,14 @@ class AttributeHandler(object):
self._catcache.pop(catkey, None) self._catcache.pop(catkey, None)
self._cache_complete = False self._cache_complete = False
def reset_cache(self):
"""
Reset cache from the outside.
"""
self._cache_complete = False
self._cache = {}
self._catcache = {}
def has(self, key=None, category=None): def has(self, key=None, category=None):
""" """
Checks if the given Attribute (or list of Attributes) exists on Checks if the given Attribute (or list of Attributes) exists on
@ -910,7 +918,6 @@ class NAttributeHandler(object):
""" """
self._store[key] = value self._store[key] = value
self.obj.set_recache_protection()
def remove(self, key): def remove(self, key):
""" """
@ -922,7 +929,6 @@ class NAttributeHandler(object):
""" """
if key in self._store: if key in self._store:
del self._store[key] del self._store[key]
self.obj.set_recache_protection(self._store)
def clear(self): def clear(self):
""" """

View file

@ -376,6 +376,27 @@ 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)
def at_idmapper_flush(self):
"""
This is called when the idmapper cache is flushed and
allows customized actions when this happens.
Returns:
do_flush (bool): If True, flush this object as normal. If
False, don't flush and expect this object to handle
the flushing on its own.
"""
if self.nattributes.all():
# we can't flush this object if we have non-persistent
# attributes stored - those would get lost! Nevertheless
# we try to flush as many references as we can.
self.attributes.reset_cache()
self.tags.reset_cache()
return False
# a normal flush
return True
# #
# Object manipulation methods # Object manipulation methods
# #

View file

@ -212,6 +212,13 @@ class TagHandler(object):
self._catcache.pop(catkey, None) self._catcache.pop(catkey, None)
self._cache_complete = False self._cache_complete = False
def reset_cache(self):
"""
Reset the cache from the outside.
"""
self._cache_complete = False
self._cache = {}
self._catcache = {}
def add(self, tag=None, category=None, data=None): def add(self, tag=None, category=None, data=None):
""" """

View file

@ -79,7 +79,6 @@ class SharedMemoryModelBase(ModelBase):
# the dbmodel is either the proxy base or ourselves # the dbmodel is either the proxy base or ourselves
dbmodel = cls._meta.proxy_for_model if cls._meta.proxy else cls dbmodel = cls._meta.proxy_for_model if cls._meta.proxy else cls
cls.__dbclass__ = dbmodel cls.__dbclass__ = dbmodel
dbmodel._idmapper_recache_protection = False
if not hasattr(dbmodel, "__instance_cache__"): if not hasattr(dbmodel, "__instance_cache__"):
# we store __instance_cache__ only on the dbmodel base # we store __instance_cache__ only on the dbmodel base
dbmodel.__instance_cache__ = {} dbmodel.__instance_cache__ = {}
@ -291,8 +290,10 @@ class SharedMemoryModel(with_metaclass(SharedMemoryModelBase, Model)):
""" """
try: try:
if force or not cls._idmapper_recache_protection: if force or cls.at_idmapper_flush():
del cls.__dbclass__.__instance_cache__[key] del cls.__dbclass__.__instance_cache__[key]
else:
cls._dbclass__.__instance_cache__[key].refresh_from_db()
except KeyError: except KeyError:
pass pass
@ -319,27 +320,37 @@ class SharedMemoryModel(with_metaclass(SharedMemoryModelBase, Model)):
cls.__dbclass__.__instance_cache__ = {} cls.__dbclass__.__instance_cache__ = {}
else: else:
cls.__dbclass__.__instance_cache__ = dict((key, obj) for key, obj in cls.__dbclass__.__instance_cache__.items() cls.__dbclass__.__instance_cache__ = dict((key, obj) for key, obj in cls.__dbclass__.__instance_cache__.items()
if obj._idmapper_recache_protection) if not obj.at_idmapper_flush())
for ins in cls.__dbclass__.__instance_cache__.itervalues():
ins.refresh_from_db()
#flush_instance_cache = classmethod(flush_instance_cache) #flush_instance_cache = classmethod(flush_instance_cache)
# per-instance methods # per-instance methods
def at_idmapper_flush(self):
"""
This is called when the idmapper cache is flushed and
allows customized actions when this happens.
Returns:
do_flush (bool): If True, flush this object as normal. If
False, don't flush and expect this object to handle
the flushing on its own.
"""
return True
def flush_from_cache(self, force=False): def flush_from_cache(self, force=False):
""" """
Flush this instance from the instance cache. Use Flush this instance from the instance cache. Use
`force` to override recache_protection for the object. `force` to override the result of at_idmapper_flush() for the object.
""" """
pk = self._get_pk_val() pk = self._get_pk_val()
if pk and (force or not self._idmapper_recache_protection): if pk:
self.__class__.__dbclass__.__instance_cache__.pop(pk, None) if force or self.at_idmapper_flush():
self.__class__.__dbclass__.__instance_cache__.pop(pk, None)
def set_recache_protection(self, mode=True): else:
""" self.refresh_from_db()
Set if this instance should be allowed to be recached.
"""
self._idmapper_recache_protection = bool(mode)
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
""" """
@ -415,7 +426,6 @@ class WeakSharedMemoryModelBase(SharedMemoryModelBase):
def _prepare(cls): def _prepare(cls):
super(WeakSharedMemoryModelBase, cls)._prepare() super(WeakSharedMemoryModelBase, cls)._prepare()
cls.__dbclass__.__instance_cache__ = WeakValueDictionary() cls.__dbclass__.__instance_cache__ = WeakValueDictionary()
cls._idmapper_recache_protection = False
class WeakSharedMemoryModel(with_metaclass(WeakSharedMemoryModelBase, SharedMemoryModel)): class WeakSharedMemoryModel(with_metaclass(WeakSharedMemoryModelBase, SharedMemoryModel)):
@ -429,10 +439,9 @@ class WeakSharedMemoryModel(with_metaclass(WeakSharedMemoryModelBase, SharedMemo
def flush_cache(**kwargs): def flush_cache(**kwargs):
""" """
Flush idmapper cache. When doing so the cache will Flush idmapper cache. When doing so the cache will fire the
look for a property `_idmapper_cache_flush_safe` on the at_idmapper_flush hook to allow the object to optionally handle
class/subclass instance and only flush if this its own flushing.
is `True`.
Uses a signal so we make sure to catch cascades. Uses a signal so we make sure to catch cascades.