Fixed a bug in idmapper cache related to proxies. It caused issuses with cleaning cache from the proxy level. All caching is now done on the base model.

This commit is contained in:
Griatch 2015-02-08 17:26:41 +01:00
parent 2fc15bbed5
commit 469e9c73c1

View file

@ -73,19 +73,13 @@ class SharedMemoryModelBase(ModelBase):
Prepare the cache, making sure that proxies of the same db base Prepare the cache, making sure that proxies of the same db base
share the same cache. share the same cache.
""" """
def prep(dbmodel): # the dbmodel is either the proxy base or ourselves
if not hasattr(dbmodel, "__instance_cache__"): dbmodel = cls._meta.proxy_for_model if cls._meta.proxy else cls
dbmodel.__instance_cache__ = {} cls.__dbclass__ = dbmodel
dbmodel._idmapper_recache_protection = False dbmodel._idmapper_recache_protection = False
if not cls._meta.proxy: if not hasattr(dbmodel, "__instance_cache__"):
# non-proxy models get the full cache # we store __instance_cache__ only on the dbmodel base
prep(cls) dbmodel.__instance_cache__ = {}
else:
# proxies get a reference to the cache
dbmodel = cls._meta.proxy_for_model
prep(dbmodel)
cls.__instance_cache__ = dbmodel.__instance_cache__
cls._idmapper_recache_protection = False
super(SharedMemoryModelBase, cls)._prepare() super(SharedMemoryModelBase, cls)._prepare()
def __new__(cls, name, bases, attrs): def __new__(cls, name, bases, attrs):
@ -99,6 +93,7 @@ class SharedMemoryModelBase(ModelBase):
attrs["typename"] = cls.__name__ attrs["typename"] = cls.__name__
attrs["path"] = "%s.%s" % (attrs["__module__"], name) attrs["path"] = "%s.%s" % (attrs["__module__"], name)
attrs["_is_deleted"] = False
# set up the typeclass handling only if a variable _is_typeclass is set on the class # set up the typeclass handling only if a variable _is_typeclass is set on the class
def create_wrapper(cls, fieldname, wrappername, editable=True, foreignkey=False): def create_wrapper(cls, fieldname, wrappername, editable=True, foreignkey=False):
@ -113,12 +108,7 @@ class SharedMemoryModelBase(ModelBase):
"Wrapper for returing foreignkey fields" "Wrapper for returing foreignkey fields"
if _GA(cls, "_is_deleted"): if _GA(cls, "_is_deleted"):
raise ObjectDoesNotExist("Cannot access %s: Hosting object was already deleted." % fname) raise ObjectDoesNotExist("Cannot access %s: Hosting object was already deleted." % fname)
value = _GA(cls, fieldname) return _GA(cls, fieldname)
#print "_get_foreign:value:", value
try:
return _GA(value, "typeclass")
except:
return value
def _set_nonedit(cls, fname, value): def _set_nonedit(cls, fname, value):
"Wrapper for blocking editing of field" "Wrapper for blocking editing of field"
raise FieldError("Field %s cannot be edited." % fname) raise FieldError("Field %s cannot be edited." % fname)
@ -200,6 +190,9 @@ class SharedMemoryModelBase(ModelBase):
class SharedMemoryModel(Model): class SharedMemoryModel(Model):
"""
Base class for idmapped objects. Inherit from this.
"""
# CL: setting abstract correctly to allow subclasses to inherit the default # CL: setting abstract correctly to allow subclasses to inherit the default
# manager. # manager.
__metaclass__ = SharedMemoryModelBase __metaclass__ = SharedMemoryModelBase
@ -209,10 +202,6 @@ class SharedMemoryModel(Model):
class Meta: class Meta:
abstract = True abstract = True
#def __init__(cls, *args, **kwargs):
# super(SharedMemoryModel, cls).__init__(*args, **kwargs)
# cls.__idmapper_recache_protection = False
@classmethod @classmethod
def _get_cache_key(cls, args, kwargs): def _get_cache_key(cls, args, kwargs):
""" """
@ -244,6 +233,8 @@ class SharedMemoryModel(Model):
return result return result
#_get_cache_key = classmethod(_get_cache_key) #_get_cache_key = classmethod(_get_cache_key)
@classmethod @classmethod
def get_cached_instance(cls, id): def get_cached_instance(cls, id):
""" """
@ -251,8 +242,7 @@ class SharedMemoryModel(Model):
(which will always be the case when caching is disabled for this class). Please (which will always be the case when caching is disabled for this class). Please
note that the lookup will be done even when instance caching is disabled. note that the lookup will be done even when instance caching is disabled.
""" """
return cls.__instance_cache__.get(id) return cls.__dbclass__.__instance_cache__.get(id)
#get_cached_instance = classmethod(get_cached_instance)
@classmethod @classmethod
def cache_instance(cls, instance): def cache_instance(cls, instance):
@ -260,21 +250,19 @@ class SharedMemoryModel(Model):
Method to store an instance in the cache. Method to store an instance in the cache.
""" """
if instance._get_pk_val() is not None: if instance._get_pk_val() is not None:
cls.__dbclass__.__instance_cache__[instance._get_pk_val()] = instance
cls.__instance_cache__[instance._get_pk_val()] = instance
#cache_instance = classmethod(cache_instance)
@classmethod @classmethod
def get_all_cached_instances(cls): def get_all_cached_instances(cls):
"return the objects so far cached by idmapper for this class." "return the objects so far cached by idmapper for this class."
return cls.__instance_cache__.values() return cls.__dbclass__.__instance_cache__.values()
@classmethod @classmethod
def _flush_cached_by_key(cls, key, force=True): def _flush_cached_by_key(cls, key, force=True):
"Remove the cached reference." "Remove the cached reference."
try: try:
if force or not cls._idmapper_recache_protection: if force or not cls._idmapper_recache_protection:
del cls.__instance_cache__[key] del cls.__dbclass__.__instance_cache__[key]
except KeyError: except KeyError:
pass pass
@ -297,18 +285,33 @@ class SharedMemoryModel(Model):
keyword to remove all objects, safe or not. keyword to remove all objects, safe or not.
""" """
if force: if force:
cls.__instance_cache__ = {} cls.__dbclass__.__instance_cache__ = {}
else: else:
cls.__instance_cache__ = dict((key, obj) for key, obj in cls.__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 obj._idmapper_recache_protection)
#flush_instance_cache = classmethod(flush_instance_cache) #flush_instance_cache = classmethod(flush_instance_cache)
# per-instance methods # per-instance methods
def flush_from_cache(self, force=False):
"""
Flush this instance from the instance cache. Use
force to override recache_protection for the object.
"""
if self.pk and (force or not self._idmapper_recache_protection):
self.__class__.__dbclass__.__instance_cache__.pop(self.pk, None)
def set_recache_protection(self, mode=True): def set_recache_protection(self, mode=True):
"set if this instance should be allowed to be recached." "set if this instance should be allowed to be recached."
self._idmapper_recache_protection = bool(mode) self._idmapper_recache_protection = bool(mode)
def delete(self, *args, **kwargs):
"""
Delete the object, clearing cache
"""
self.flush_from_cache()
super(SharedMemoryModel, self).delete(*args, **kwargs)
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
"save method tracking process/thread issues" "save method tracking process/thread issues"
@ -335,7 +338,7 @@ class WeakSharedMemoryModelBase(SharedMemoryModelBase):
""" """
def _prepare(cls): def _prepare(cls):
super(WeakSharedMemoryModelBase, cls)._prepare() super(WeakSharedMemoryModelBase, cls)._prepare()
cls.__instance_cache__ = WeakValueDictionary() cls.__dbclass__.__instance_cache__ = WeakValueDictionary()
cls._idmapper_recache_protection = False cls._idmapper_recache_protection = False
@ -367,7 +370,9 @@ def flush_cache(**kwargs):
else: else:
yield cls yield cls
#print "start flush ..."
for cls in class_hierarchy([SharedMemoryModel]): for cls in class_hierarchy([SharedMemoryModel]):
#print cls
cls.flush_instance_cache() cls.flush_instance_cache()
# run the python garbage collector # run the python garbage collector
return gc.collect() return gc.collect()