Commit 818315b6 authored by Shane Hathaway's avatar Shane Hathaway

Fixed intermittent failures by making MVCCMappingStorage hold a per-connection...

Fixed intermittent failures by making MVCCMappingStorage hold a per-connection snapshot of the database.
parent c160bc59
...@@ -231,7 +231,8 @@ class MappingStorage(object): ...@@ -231,7 +231,8 @@ class MappingStorage(object):
if transactions[tid].pack(oid): if transactions[tid].pack(oid):
del transactions[tid] del transactions[tid]
self._data = new_data self._data.clear()
self._data.update(new_data)
# ZODB.interfaces.IStorage # ZODB.interfaces.IStorage
def registerDB(self, db): def registerDB(self, db):
...@@ -307,6 +308,7 @@ class MappingStorage(object): ...@@ -307,6 +308,7 @@ class MappingStorage(object):
self._ltid = tid self._ltid = tid
self._transactions[tid] = TransactionRecord(tid, transaction, tdata) self._transactions[tid] = TransactionRecord(tid, transaction, tdata)
self._transaction = None self._transaction = None
del self._tdata
self._commit_lock.release() self._commit_lock.release()
# ZEO.interfaces.IServeable # ZEO.interfaces.IServeable
......
...@@ -20,6 +20,8 @@ connection's view. ...@@ -20,6 +20,8 @@ connection's view.
import time import time
import BTrees import BTrees
import ZODB.utils
import ZODB.POSException
from ZODB.interfaces import IMVCCStorage from ZODB.interfaces import IMVCCStorage
from ZODB.MappingStorage import MappingStorage from ZODB.MappingStorage import MappingStorage
from ZODB.TimeStamp import TimeStamp from ZODB.TimeStamp import TimeStamp
...@@ -33,56 +35,102 @@ class MVCCMappingStorage(MappingStorage): ...@@ -33,56 +35,102 @@ class MVCCMappingStorage(MappingStorage):
MappingStorage.__init__(self, name=name) MappingStorage.__init__(self, name=name)
# _polled_tid contains the transaction ID at the last poll. # _polled_tid contains the transaction ID at the last poll.
self._polled_tid = '' self._polled_tid = ''
self._data_snapshot = None # {oid->(state, tid)}
self._main_lock_acquire = self._lock_acquire
self._main_lock_release = self._lock_release
def new_instance(self): def new_instance(self):
"""Returns a storage instance that is a view of the same data. """Returns a storage instance that is a view of the same data.
""" """
res = MVCCMappingStorage(name=self.__name__) inst = MVCCMappingStorage(name=self.__name__)
res._transactions = self._transactions # All instances share the same OID data, transaction log, commit lock,
return res # and OID sequence.
inst._data = self._data
inst._transactions = self._transactions
inst._commit_lock = self._commit_lock
inst.new_oid = self.new_oid
inst.pack = self.pack
inst._main_lock_acquire = self._lock_acquire
inst._main_lock_release = self._lock_release
return inst
@ZODB.utils.locked(MappingStorage.opened)
def sync(self, force=False): def sync(self, force=False):
pass self._data_snapshot = None
def release(self): def release(self):
pass pass
@ZODB.utils.locked(MappingStorage.opened)
def load(self, oid, version=''):
assert not version, "Versions are not supported"
if self._data_snapshot is None:
self.poll_invalidations()
info = self._data_snapshot.get(oid)
if info:
return info
raise ZODB.POSException.POSKeyError(oid)
def poll_invalidations(self): def poll_invalidations(self):
"""Poll the storage for changes by other connections. """Poll the storage for changes by other connections.
""" """
if self._transactions: # prevent changes to _transactions and _data during analysis
new_tid = self._transactions.maxKey() self._main_lock_acquire()
else: try:
new_tid = ''
if self._transactions:
if self._polled_tid: new_tid = self._transactions.maxKey()
if not self._transactions.has_key(self._polled_tid): else:
# This connection is so old that we can no longer enumerate new_tid = ''
# all the changes.
self._polled_tid = new_tid # Copy the current data into a snapshot. This is obviously
return None # very inefficient for large storages, but it's good for
# tests.
changed_oids = set() self._data_snapshot = {}
for tid, txn in self._transactions.items( for oid, tid_data in self._data.items():
self._polled_tid, new_tid, excludemin=True, excludemax=False): if tid_data:
if txn.status == 'p': tid = tid_data.maxKey()
# This transaction has been packed, so it is no longer self._data_snapshot[oid] = tid_data[tid], tid
# possible to enumerate all changed oids.
self._polled_tid = new_tid if self._polled_tid:
return None if not self._transactions.has_key(self._polled_tid):
if tid == self._ltid: # This connection is so old that we can no longer enumerate
# ignore the transaction committed by this connection # all the changes.
continue self._polled_tid = new_tid
return None
changes = txn.data
# pull in changes from the transaction log changed_oids = set()
for oid, value in changes.iteritems(): for tid, txn in self._transactions.items(
tid_data = self._data.get(oid) self._polled_tid, new_tid,
if tid_data is None: excludemin=True, excludemax=False):
tid_data = BTrees.OOBTree.OOBucket() if txn.status == 'p':
self._data[oid] = tid_data # This transaction has been packed, so it is no longer
tid_data[tid] = changes[oid] # possible to enumerate all changed oids.
changed_oids.update(changes.keys()) self._polled_tid = new_tid
return None
if tid == self._ltid:
# ignore the transaction committed by this connection
continue
changed_oids.update(txn.data.keys())
finally:
self._main_lock_release()
self._polled_tid = new_tid self._polled_tid = new_tid
return list(changed_oids) return list(changed_oids)
def tpc_finish(self, transaction, func = lambda tid: None):
self._data_snapshot = None
MappingStorage.tpc_finish(self, transaction, func)
def tpc_abort(self, transaction):
self._data_snapshot = None
MappingStorage.tpc_abort(self, transaction)
def pack(self, t, referencesf, gc=True):
# prevent all concurrent commits during packing
self._commit_lock.acquire()
try:
MappingStorage.pack(self, t, referencesf, gc)
finally:
self._commit_lock.release()
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