/usr/share/pyshared/axiom/_fincache.py is in python-axiom 0.7.1-2.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 | from weakref import ref
from traceback import print_exc
from twisted.python import log
from axiom import iaxiom
class CacheFault(KeyError):
"""
An item has fallen out of cache, but the weakref callback has not yet run.
"""
class CacheInconsistency(RuntimeError):
"""
A key being cached is already present in the cache.
"""
def logErrorNoMatterWhat():
try:
log.msg("Exception in finalizer cannot be propagated")
log.err()
except:
try:
emergLog = file("WEAKREF_EMERGENCY_ERROR.log", 'a')
print_exc(file=emergLog)
emergLog.flush()
emergLog.close()
except:
# Nothing can be done. We can't get an emergency log file to write
# to. Don't bother.
return
def createCacheRemoveCallback(cacheRef, key, finalizer):
"""
Construct a callable to be used as a weakref callback for cache entries.
The callable will invoke the provided finalizer, as well as removing the
cache entry if the cache still exists and contains an entry for the given
key.
@type cacheRef: L{weakref.ref} to L{FinalizingCache}
@param cacheRef: A weakref to the cache in which the corresponding cache
item was stored.
@param key: The key for which this value is cached.
@type finalizer: callable taking 0 arguments
@param finalizer: A user-provided callable that will be called when the
weakref callback runs.
"""
def remove(self):
# Weakref callbacks cannot raise exceptions or DOOM ensues
try:
finalizer()
except:
logErrorNoMatterWhat()
try:
self = cacheRef()
if self is not None:
try:
del self.data[key]
except KeyError:
# FinalizingCache.get may have already removed the cache
# item from the dictionary; see the comment in that method
# for an explanation of why.
pass
except:
logErrorNoMatterWhat()
return remove
class FinalizingCache:
"""
A cache that stores values by weakref.
A finalizer is invoked when the weakref to a cached value is broken.
@type data: L{dict}
@ivar data: The cached values.
"""
def __init__(self):
self.data = {}
def cache(self, key, value):
"""
Add an entry to the cache.
A weakref to the value is stored, rather than a direct reference. The
value must have a C{__finalizer__} method that returns a callable which
will be invoked when the weakref is broken.
@param key: The key identifying the cache entry.
@param value: The value for the cache entry.
"""
fin = value.__finalizer__()
try:
# It's okay if there's already a cache entry for this key as long
# as the weakref has already been broken. See the comment in
# get() for an explanation of why this might happen.
if self.data[key]() is not None:
raise CacheInconsistency(
"Duplicate cache key: %r %r %r" % (
key, value, self.data[key]))
except KeyError:
pass
self.data[key] = ref(value, createCacheRemoveCallback(
ref(self), key, fin))
return value
def uncache(self, key, value):
"""
Remove a key from the cache.
As a sanity check, if the specified key is present in the cache, it
must have the given value.
@param key: The key to remove.
@param value: The expected value for the key.
"""
try:
assert self.get(key) is value
del self.data[key]
except KeyError:
# If the entry has already been removed from the cache, this will
# result in KeyError which we ignore. If the entry is still in the
# cache, but the weakref has been broken, this will result in
# CacheFault (a KeyError subclass) which we also ignore. See the
# comment in get() for an explanation of why this might happen.
pass
def get(self, key):
"""
Get an entry from the cache by key.
@raise KeyError: if the given key is not present in the cache.
@raise CacheFault: (a L{KeyError} subclass) if the given key is present
in the cache, but the value it points to is gone.
"""
o = self.data[key]()
if o is None:
# On CPython, the weakref callback will always(?) run before any
# other code has a chance to observe that the weakref is broken;
# and since the callback removes the item from the dict, this
# branch of code should never run. However, on PyPy (and possibly
# other Python implementations), the weakref callback does not run
# immediately, thus we may be able to observe this intermediate
# state. Should this occur, we remove the dict item ourselves,
# and raise CacheFault (which is a KeyError subclass).
del self.data[key]
raise CacheFault(
"FinalizingCache has %r but its value is no more." % (key,))
log.msg(interface=iaxiom.IStatEvent, stat_cache_hits=1, key=key)
return o
|