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 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326
|
"""Per-Transaction Cache Management
ZPatterns objects sometimes need to reset certain cached data at transaction
boundaries. For example, DataSkins clear their attribute cache and
state flags, while Racks clear their item cache. To do this, they subclass
from 'ZPatterns.Transactions.Kept', define a '__per_transaction_cache_attrs__'
attribute listing the names of attributes they need cleared, and reference
the 'self._v_Keeper' attribute just before changing any cached attributes.
That's pretty much all that's required to make an object with per-transaction
caching capabilities.
**DO NOT USE THE DEPRECATED CLASSES**. They contain some serious bugs based on
a few critical misunderstandings of the Zope transaction state machinery, such
as the woefully misguided belief that there is any way to know whether a Zope
transaction is finished. In fact, only the Zope transaction knows if it's
finished, and it doesn't tell anybody else. :(
"""
from ExtensionClass import Base
from ComputedAttribute import ComputedAttribute
class Keeper:
"""Resets an object's per-transaction cache attributes at txn boundaries
Note that Keepers are created by Kept objects semi-automatically, and
there is usually no need to create one manually. A Keeper automatically
registers itself with the Zope transaction upon creation, and instructs
its Kept client to clear its per-transaction cache at all transaction
boundaries. Keeper methods are called only by the Zope transaction, so
don't mess with them."""
# tpc_begin, tpc_vote, commit, commit_sub: ignore
# tpc_finish, tpc_abort, abort, abort_sub: clear
def __init__(self,client):
self.client = client
get_transaction().register(self)
def ClearCache(self,*args):
self.client._clearPerTransactionCache()
tpc_finish = tpc_abort = abort = abort_sub = ClearCache
def tpc_begin(self,transaction,subtransaction=None): pass
def commit(self,object,transaction): pass
def tpc_vote(self,transaction): pass
def commit_sub(self,transaction): pass
class Kept(Base):
"""Thing which has a Keeper to clear its per-transaction cache.
Objects derived from Kept should reference the 'self._v_Keeper'
attribute whenever they need to flag that they have made changes to
their cache that would require it to be cleared. (Note that '_v_Keeper'
is an *attribute*, not a method, and so should not be called, just
referenced.)
Once this has been done, the next transaction state transition
that occurs (sub/main transaction commit or abort) will cause
the object's Keeper to call for a cache reset.
Subclasses of Kept should define a '__per_transaction_cache_attrs__'
attribute as a sequence of attributes which they would like to have
deleted from their '__dict__' at reset time.
"""
__per_transaction_cache_attrs__ = ()
def _clearPerTransactionCache(self):
"""Get rid of stale data from previous transactions"""
d=self.__dict__; have=d.has_key
for a in self.__per_transaction_cache_attrs__:
if have(a): del d[a]
if have('_v_Keeper'): del d['_v_Keeper']
def _v_Keeper(self):
"""Our transaction keeper, which resets the cache at txn boundaries"""
v = self._v_Keeper = Keeper(self); return v
_v_Keeper = ComputedAttribute(_v_Keeper)
class Transactional:
"""
DEPRECATED Mix-in Adapter to simplify ZODB Transaction messages
DO NOT USE THIS!!!
Whenever your subclass does work which requires transactional behavior,
it should call self._register(), to ensure it is registered with the current
transaction. Your subclass will then be able to receive the following
(translated) messages from the ZODB transaction:
_checkpoint() --
apply work done so far (this is where committing should mostly go)
_revert() --
abort work done since last checkpoint or since the transaction began
if not yet checkpointed
_rollback() --
abort all checkpointed work (this call is always preceded by a _revert()
call to handle any work which was not yet checkpointed, and is always
followed by a _cleanup() call, so don't make it do the work of either.)
_vote() --
raise an exception here if you want to force the transaction to abort
_finalize() --
make all work done permanent (try not to refer to objects' states here)
_cleanup() --
called whenever a transaction is terminated, whatever its outcome
Your revert, rollback, finalize, and cleanup methods should not raise any
exceptions. An untrapped exception from _finalize() (or _cleanup() after
_finalize()) will put the entire ZODB application into "hosed" state which
can only be undone by an application restart. The ZODB transaction will
in most cases silently toss exceptions from your _revert() and _rollback()
routines, so it would be a good idea to log them somewhere.
"""
_v_registered = None # Are we registered w/a transaction?
def _register(self):
if self._v_registered: return
get_transaction().register(Reporter(self))
self._v_registered = 1
def _unregister(self):
try:
del self._v_registered
except:
pass
# From here down, override as you see fit...
def _checkpoint(self):
"""Called during commit() phase of transaction/subtransaction;
should do all the "real work" of committing"""
pass
def _revert(self):
"""Do the real work of aborting anything here"""
pass
def _rollback(self):
"""Called during tpc_abort - should do as little as possible"""
pass
def _vote(self):
"""Opportunity to raise an error to prevent commit"""
pass
def _finalize(self):
"""Called during tpc_finish - should do as little as possible"""
pass
def _cleanup(self):
"""Called after transaction is effectively *over*, so don't
do anything here that either 1) isn't idempotent, or 2)
requires knowledge about the state/data of the transaction."""
pass
class Reporter:
# DEPRECATED Event translator for Transactionals
tpc_entered = 0 # Are we in tpc yet?
_sub = None # Active subtransaction?
def __init__(self, client):
self.client = client
def tpc_begin(self, transaction, subtransaction=None):
self.tpc_entered = 1
self._sub = subtransaction
def tpc_abort(self,transaction):
try:
self.client._revert()
self.client._rollback()
finally:
self.end_tran()
def tpc_vote(self,transaction):
self.client._vote()
def tpc_finish(self,transaction):
if self._sub: return
try:
self.client._finalize()
finally:
self.end_tran()
def commit_sub(self, transaction):
self._sub = None
def abort_sub(self, transaction):
self._sub = None
self.tpc_abort()
def commit(self, object, transaction):
self.client._checkpoint()
def abort(self, object, transaction):
try:
self.client._revert()
finally:
if not self.tpc_entered:
# Abort before tpc_begin() means transaction aborted early
self.end_tran()
def end_tran(self):
try:
self.client._cleanup()
finally:
self.client._unregister()
from UserDict import UserDict
class TransientMapping(Transactional, UserDict):
"""DEPRECATED A mapping that clears itself after every transaction"""
saved = None
def __setitem__(self, key, item):
self._register()
UserDict.__setitem__.im_func(self, key, item)
def __delitem__(self, key):
self._register()
UserDict.__delitem__.im_func(self, key)
def update(self, dict):
self._register()
UserDict.update.im_func(self, dict)
def clear(self):
self._register()
UserDict.clear.im_func(self)
def copy(self):
return self.data.copy()
def _checkpoint(self):
self.saved = self.data.copy()
def _revert(self):
if self.saved:
self.data = self.saved
del self.saved
# No need to define rollback, since cleanup always follows
def _cleanup(self):
UserDict.clear.im_func(self)
Transactional._cleanup.im_func(self)
|