Commit f938538e authored by Chris McDonough's avatar Chris McDonough

Changed revision cache gc algorithm to be better on memory, addd revision cache gc tests.

parent ad4428ab
...@@ -89,24 +89,22 @@ MappingStorage. Unlike MappingStorage, it needs not be packed to get rid of ...@@ -89,24 +89,22 @@ MappingStorage. Unlike MappingStorage, it needs not be packed to get rid of
non-cyclic garbage and it does rudimentary conflict resolution. This is a non-cyclic garbage and it does rudimentary conflict resolution. This is a
ripoff of Jim's Packless bsddb3 storage. ripoff of Jim's Packless bsddb3 storage.
$Id: TemporaryStorage.py,v 1.4 2001/11/21 03:13:37 chrism Exp $ $Id: TemporaryStorage.py,v 1.5 2001/11/26 15:34:56 chrism Exp $
""" """
__version__ ='$Revision: 1.4 $'[11:-2] __version__ ='$Revision: 1.5 $'[11:-2]
from zLOG import LOG from zLOG import LOG
from struct import pack, unpack
from ZODB.referencesf import referencesf from ZODB.referencesf import referencesf
from ZODB import POSException from ZODB import POSException
from ZODB.BaseStorage import BaseStorage from ZODB.BaseStorage import BaseStorage
from ZODB.ConflictResolution import ConflictResolvingStorage, ResolvedSerial,\ from ZODB.ConflictResolution import ConflictResolvingStorage, ResolvedSerial
bad_classes, bad_class, _classFactory, PersistentReference, \ import time
PersistentReferenceFactory, persistent_id, state
from cStringIO import StringIO
from cPickle import Unpickler, Pickler
# number of transactions for which to keep prior object revisions # keep old object revisions for CONFLICT_CACHE_MAXAGE seconds
CONFLICT_CACHE_SIZE = 100 CONFLICT_CACHE_MAXAGE = 60
# garbage collect conflict cache every CONFLICT_CACHE_GCEVERY seconds
CONFLICT_CACHE_GCEVERY = 60
class ReferenceCountError(POSException.POSError): class ReferenceCountError(POSException.POSError):
""" An error occured while decrementing a reference to an object in """ An error occured while decrementing a reference to an object in
...@@ -128,7 +126,7 @@ class TemporaryStorage(BaseStorage, ConflictResolvingStorage): ...@@ -128,7 +126,7 @@ class TemporaryStorage(BaseStorage, ConflictResolvingStorage):
opickle -- mapping of oid to pickle opickle -- mapping of oid to pickle
_tmp -- used by 'store' to collect changes before finalization _tmp -- used by 'store' to collect changes before finalization
_conflict_cache -- cache of recently-written object revisions _conflict_cache -- cache of recently-written object revisions
_transaction_counter -- rotating counter used in conflict resolution _last_cache_gc -- last time that conflict cache was garbage collected
""" """
BaseStorage.__init__(self, name) BaseStorage.__init__(self, name)
...@@ -138,7 +136,7 @@ class TemporaryStorage(BaseStorage, ConflictResolvingStorage): ...@@ -138,7 +136,7 @@ class TemporaryStorage(BaseStorage, ConflictResolvingStorage):
self._opickle={} self._opickle={}
self._tmp = [] self._tmp = []
self._conflict_cache = {} self._conflict_cache = {}
self._transaction_counter = 0 self._last_cache_gc = 0
self._oid = '\0\0\0\0\0\0\0\0' self._oid = '\0\0\0\0\0\0\0\0'
def __len__(self): def __len__(self):
...@@ -148,11 +146,14 @@ class TemporaryStorage(BaseStorage, ConflictResolvingStorage): ...@@ -148,11 +146,14 @@ class TemporaryStorage(BaseStorage, ConflictResolvingStorage):
return 0 return 0
def _clear_temp(self): def _clear_temp(self):
now = time.time()
if now > (self._last_cache_gc + CONFLICT_CACHE_GCEVERY):
for k, v in self._conflict_cache.items():
data, t = v
if now > (t + CONFLICT_CACHE_MAXAGE):
del self._conflict_cache[k]
self._last_cache_gc = now
self._tmp = [] self._tmp = []
if self._transaction_counter % CONFLICT_CACHE_SIZE == 0:
# clear the revision cache before we run out of RAM.
self._transaction_counter = 0
self._conflict_cache = {}
def close(self): def close(self):
""" """
...@@ -174,7 +175,7 @@ class TemporaryStorage(BaseStorage, ConflictResolvingStorage): ...@@ -174,7 +175,7 @@ class TemporaryStorage(BaseStorage, ConflictResolvingStorage):
storage needs! """ storage needs! """
self._lock_acquire() self._lock_acquire()
try: try:
data = self._conflict_cache.get((oid, serial), marker) data, t = self._conflict_cache.get((oid, serial), marker)
if data is marker: if data is marker:
raise POSException.ConflictError, (oid, serial) raise POSException.ConflictError, (oid, serial)
return data return data
...@@ -200,7 +201,8 @@ class TemporaryStorage(BaseStorage, ConflictResolvingStorage): ...@@ -200,7 +201,8 @@ class TemporaryStorage(BaseStorage, ConflictResolvingStorage):
oserial = serial oserial = serial
newserial=self._serial newserial=self._serial
self._tmp.append((oid, data)) self._tmp.append((oid, data))
self._conflict_cache[(oid, newserial)] = data now = time.time()
self._conflict_cache[(oid, newserial)] = data, now
return serial == oserial and newserial or ResolvedSerial return serial == oserial and newserial or ResolvedSerial
finally: finally:
self._lock_release() self._lock_release()
...@@ -279,8 +281,7 @@ class TemporaryStorage(BaseStorage, ConflictResolvingStorage): ...@@ -279,8 +281,7 @@ class TemporaryStorage(BaseStorage, ConflictResolvingStorage):
if oid == '\0\0\0\0\0\0\0\0': continue if oid == '\0\0\0\0\0\0\0\0': continue
self._takeOutGarbage(oid) self._takeOutGarbage(oid)
self._transaction_counter = self._transaction_counter + 1 self._tmp = []
self._clear_temp()
def _takeOutGarbage(self, oid): def _takeOutGarbage(self, oid):
# take out the garbage. # take out the garbage.
......
...@@ -4,8 +4,11 @@ if __name__=='__main__': ...@@ -4,8 +4,11 @@ if __name__=='__main__':
sys.path.insert(0, '..') sys.path.insert(0, '..')
import ZODB import ZODB
from ZODB.tests.MinPO import MinPO
from Products.TemporaryFolder import TemporaryStorage from Products.TemporaryFolder import TemporaryStorage
import sys, os, unittest from Products.TemporaryFolder.TemporaryStorage import CONFLICT_CACHE_MAXAGE,\
CONFLICT_CACHE_GCEVERY
import sys, os, unittest, time
from ZODB.tests import StorageTestBase, BasicStorage, \ from ZODB.tests import StorageTestBase, BasicStorage, \
Synchronization, ConflictResolution, \ Synchronization, ConflictResolution, \
...@@ -13,7 +16,7 @@ from ZODB.tests import StorageTestBase, BasicStorage, \ ...@@ -13,7 +16,7 @@ from ZODB.tests import StorageTestBase, BasicStorage, \
class TemporaryStorageTests( class TemporaryStorageTests(
StorageTestBase.StorageTestBase, StorageTestBase.StorageTestBase,
RevisionStorage.RevisionStorage, # not actually a revision storage, but.. RevisionStorage.RevisionStorage, # not a revision storage, but passes
BasicStorage.BasicStorage, BasicStorage.BasicStorage,
Synchronization.SynchronizedStorage, Synchronization.SynchronizedStorage,
ConflictResolution.ConflictResolvingStorage, ConflictResolution.ConflictResolvingStorage,
...@@ -29,6 +32,29 @@ class TemporaryStorageTests( ...@@ -29,6 +32,29 @@ class TemporaryStorageTests(
def tearDown(self): def tearDown(self):
StorageTestBase.StorageTestBase.tearDown(self) StorageTestBase.StorageTestBase.tearDown(self)
def checkConflictCacheIsCleared(self):
old_gcevery = TemporaryStorage.CONFLICT_CACHE_GCEVERY
old_maxage = TemporaryStorage.CONFLICT_CACHE_MAXAGE
TemporaryStorage.CONFLICT_CACHE_GCEVERY = 5
TemporaryStorage.CONFLICT_CACHE_MAXAGE = 5
try:
oid = self._storage.new_oid()
self._dostore(oid, data=MinPO(5))
time.sleep(TemporaryStorage.CONFLICT_CACHE_GCEVERY + 1)
oid2 = self._storage.new_oid()
self._dostore(oid2, data=MinPO(10))
oid3 = self._storage.new_oid()
self._dostore(oid3, data=MinPO(9))
assert len(self._storage._conflict_cache) == 2
time.sleep(TemporaryStorage.CONFLICT_CACHE_GCEVERY + 1)
oid4 = self._storage.new_oid()
self._dostore(oid4, data=MinPO(11))
assert len(self._storage._conflict_cache) == 1
finally:
TemporaryStorage.CONFLICT_CACHE_GCEVERY = old_gcevery
TemporaryStorage.CONFLICT_CACHE_MAXAGE = old_maxage
def test_suite(): def test_suite():
suite = unittest.makeSuite(TemporaryStorageTests, 'check') suite = unittest.makeSuite(TemporaryStorageTests, 'check')
suite2 = unittest.makeSuite(Corruption.FileStorageCorruptTests, 'check') suite2 = unittest.makeSuite(Corruption.FileStorageCorruptTests, 'check')
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment