def __dump(self, storage, sorted=sorted): return { u64(t.tid): sorted( (u64(o.oid), o.data_txn and u64(o.data_txn), None if o.data is None else makeChecksum(o.data)) for o in t) for t in storage.iterator() }
def _deleteRange(self, partition, min_tid=None, max_tid=None): sql = " WHERE `partition`=%d" % partition if min_tid: sql += " AND %d < tid" % util.u64(min_tid) if max_tid: sql += " AND tid <= %d" % util.u64(max_tid) q = self.query q("DELETE FROM trans" + sql) sql = " FROM obj" + sql data_id_list = [x for x, in q("SELECT DISTINCT data_id" + sql) if x] q("DELETE" + sql) self._pruneData(data_id_list)
def checkTIDRange(self, partition, length, min_tid, max_tid): # XXX: SQLite's GROUP_CONCAT is slow (looks like quadratic) count, tids, max_tid = self.query("""\ SELECT COUNT(*), GROUP_CONCAT(tid), MAX(tid) FROM (SELECT tid FROM trans WHERE partition=? AND ?<=tid AND tid<=? ORDER BY tid ASC LIMIT ?) AS t""", (partition, util.u64(min_tid), util.u64(max_tid), -1 if length is None else length)).fetchone() if count: return count, sha1(tids).digest(), util.p64(max_tid) return 0, ZERO_HASH, ZERO_TID
def deleteObject(self, oid, serial=None): oid = util.u64(oid) sql = " FROM obj WHERE partition=? AND oid=?" args = [self._getPartition(oid), oid] if serial: sql += " AND tid=?" args.append(util.u64(serial)) q = self.query data_id_list = [x for x, in q("SELECT DISTINCT data_id" + sql, args) if x] q("DELETE" + sql, args) self._pruneData(data_id_list)
def getLastIds(self, params): """ Get last ids. """ assert not params ptid, backup_tid, truncate_tid = self.neoctl.getRecovery() if backup_tid: ltid = self.neoctl.getLastTransaction() r = "backup_tid = 0x%x" % u64(backup_tid) else: loid, ltid = self.neoctl.getLastIds() r = "last_oid = 0x%x" % u64(loid) return r + "\nlast_tid = 0x%x\nlast_ptid = %u" % (u64(ltid), ptid)
def _nextTID(orig, tm, ttid=None, divisor=None): n = self._next_tid self._next_tid = n + 1 n = str(n).rjust(3, '-') if ttid: t = u64('T%s%s-' % (n, ttid[1:4])) m = (u64(ttid) - t) % divisor assert m < 211, (p64(t), divisor) t = p64(t + m) else: t = 'T%s----' % n assert tm._last_tid < t, (tm._last_tid, t) tm._last_tid = t return t
def getLastIds(self, params): """ Get last ids. """ assert not params ptid, backup_tid, truncate_tid = self.neoctl.getRecovery() if backup_tid: ltid = self.neoctl.getLastTransaction() r = "backup_tid = 0x%x (%s)" % (u64(backup_tid), timeStringFromTID(backup_tid)) else: loid, ltid = self.neoctl.getLastIds() r = "last_oid = 0x%x" % (u64(loid)) return r + "\nlast_tid = 0x%x (%s)\nlast_ptid = %s" % \ (u64(ltid), timeStringFromTID(ltid), ptid)
def pack(self, tid, updateObjectDataForPack): # TODO: unit test (along with updatePackFuture) p64 = util.p64 tid = util.u64(tid) updatePackFuture = self._updatePackFuture getPartition = self._getPartition q = self.query self._setPackTID(tid) for count, oid, max_serial in q("SELECT COUNT(*) - 1, oid, MAX(tid)" " FROM obj WHERE tid <= %d GROUP BY oid" % tid): partition = getPartition(oid) if q("SELECT 1 FROM obj WHERE `partition` = %d" " AND oid = %d AND tid = %d AND data_id IS NULL" % (partition, oid, max_serial)): max_serial += 1 elif not count: continue # There are things to delete for this object data_id_set = set() sql = ' FROM obj WHERE `partition`=%d AND oid=%d' \ ' AND tid<%d' % (partition, oid, max_serial) for serial, data_id in q('SELECT tid, data_id' + sql): data_id_set.add(data_id) new_serial = updatePackFuture(oid, serial, max_serial) if new_serial: new_serial = p64(new_serial) updateObjectDataForPack(p64(oid), p64(serial), new_serial, data_id) q('DELETE' + sql) data_id_set.discard(None) self._pruneData(data_id_set) self.commit()
def _deleteRange(self, partition, min_tid=None, max_tid=None): sql = " WHERE partition=?" args = [partition] if min_tid: sql += " AND ? < tid" args.append(util.u64(min_tid)) if max_tid: sql += " AND tid <= ?" args.append(util.u64(max_tid)) q = self.query q("DELETE FROM trans" + sql, args) sql = " FROM obj" + sql data_id_list = [x for x, in q("SELECT DISTINCT data_id" + sql, args) if x] q("DELETE" + sql, args) self._pruneData(data_id_list)
def getLastObjectTID(self, oid): oid = util.u64(oid) r = self.query("SELECT tid FROM obj" " WHERE partition=? AND oid=?" " ORDER BY tid DESC LIMIT 1", (self._getReadablePartition(oid), oid)).fetchone() return r and util.p64(r[0])
def testTweakVsReplication(self, cluster, done=False): S = cluster.storage_list cluster.start(S[:1]) t, c = cluster.getTransaction() ob = c.root()[''] = PCounterWithResolution() t.commit() self.assertEqual(1, u64(ob._p_oid)) for s in S[1:]: s.start() self.tic() def tweak(): self.tic() self.assertFalse(delay_list) self.assertPartitionTable(cluster, 'UU|UO') f.delayAskFetchObjects() cluster.enableStorageList(S[2:]) cluster.neoctl.tweakPartitionTable() self.tic() self.assertPartitionTable(cluster, 'UU..|F.OO') with ConnectionFilter() as f, cluster.moduloTID(1), \ Patch(S[1].replicator, _nextPartitionSortKey=lambda orig, offset: offset): delay_list = [1, 0] delay = (f.delayNotifyReplicationDone if done else f.delayAnswerFetchObjects)(lambda _: delay_list.pop()) cluster.enableStorageList((S[1], )) cluster.neoctl.tweakPartitionTable() ob._p_changed = 1 if done: tweak() t.commit() else: t2, c2 = cluster.getTransaction() c2.root()['']._p_changed = 1 l = threading.Lock() l.acquire() TransactionalResource(t2, 0, tpc_vote=lambda _: l.release()) t2 = self.newPausedThread(t2.commit) self.tic() @TransactionalResource(t, 0) def tpc_vote(_): t2.start() l.acquire() f.remove(delay) tweak() t.commit() t2.join() cluster.neoctl.dropNode(S[2].uuid) cluster.neoctl.dropNode(S[3].uuid) cluster.neoctl.tweakPartitionTable() if done: f.remove(delay) self.tic() self.assertPartitionTable(cluster, 'UU|UO') self.tic() self.assertPartitionTable(cluster, 'UU|UU') self.checkReplicas(cluster)
def pack(self, tid, updateObjectDataForPack): # TODO: unit test (along with updatePackFuture) p64 = util.p64 tid = util.u64(tid) updatePackFuture = self._updatePackFuture getPartition = self._getReadablePartition q = self.query self._setPackTID(tid) for count, oid, max_serial in q( "SELECT COUNT(*) - 1, oid, MAX(tid)" " FROM obj WHERE tid<=? GROUP BY oid", (tid, )): partition = getPartition(oid) if q( "SELECT 1 FROM obj WHERE partition=?" " AND oid=? AND tid=? AND data_id IS NULL", (partition, oid, max_serial)).fetchone(): max_serial += 1 elif not count: continue # There are things to delete for this object data_id_set = set() sql = " FROM obj WHERE partition=? AND oid=? AND tid<?" args = partition, oid, max_serial for serial, data_id in q("SELECT tid, data_id" + sql, args): data_id_set.add(data_id) new_serial = updatePackFuture(oid, serial, max_serial) if new_serial: new_serial = p64(new_serial) updateObjectDataForPack(p64(oid), p64(serial), new_serial, data_id) q("DELETE" + sql, args) data_id_set.discard(None) self._pruneData(data_id_set) self.commit()
def changePartitionTable(self, ptid, num_replicas, cell_list, reset=False): my_nid = self.getUUID() pt = dict(self.iterAssignedCells()) # In backup mode, the last transactions of a readable cell may be # incomplete. backup_tid = self.getBackupTID() if backup_tid: backup_tid = util.u64(backup_tid) def outofdate_tid(offset): tid = pt.get(offset, 0) if tid >= 0: return tid return -tid in READABLE and (backup_tid or max( self._getLastIDs(offset)[0], self._getLastTID(offset))) or 0 cell_list = [ (offset, nid, (None if state == CellStates.DISCARDED else -state if nid != my_nid or state != CellStates.OUT_OF_DATE else outofdate_tid(offset))) for offset, nid, state in cell_list ] self._changePartitionTable(cell_list, reset) self._updateReadable(reset) assert isinstance(ptid, (int, long)), ptid self._setConfiguration('ptid', str(ptid)) self._setConfiguration('replicas', str(num_replicas))
def iterCellNextTIDs(self): p64 = util.p64 backup_tid = self.getBackupTID() if backup_tid: next_tid = util.u64(backup_tid) if next_tid: next_tid += 1 for offset, tid in self.iterAssignedCells(): if tid >= 0: # OUT_OF_DATE yield offset, p64(tid and tid + 1) elif -tid in READABLE: if backup_tid: # An UP_TO_DATE cell does not have holes so it's fine to # resume from the last found records. tid = self._getLastTID(offset) yield offset, ( # For trans, a transaction can't be partially # replicated, so replication can resume from the next # possible tid. p64(max(next_tid, tid + 1) if tid else next_tid), # For obj, the last transaction may be partially # replicated so it must be checked again (i.e. no +1). p64(max(next_tid, self._getLastIDs(offset)[0]))) else: yield offset, None
def getLastObjectTID(self, oid): oid = util.u64(oid) r = self.query("SELECT tid FROM obj" " WHERE partition=? AND oid=?" " ORDER BY tid DESC LIMIT 1", (self._getPartition(oid), oid)).fetchone() return r and util.p64(r[0])
def checkTIDRange(self, partition, length, min_tid, max_tid): count, tid_checksum, max_tid = self.query( """SELECT COUNT(*), SHA1(GROUP_CONCAT(tid SEPARATOR ",")), MAX(tid) FROM (SELECT tid FROM trans WHERE `partition` = %(partition)s AND tid >= %(min_tid)d AND tid <= %(max_tid)d ORDER BY tid ASC %(limit)s) AS t""" % { 'partition': partition, 'min_tid': util.u64(min_tid), 'max_tid': util.u64(max_tid), 'limit': '' if length is None else 'LIMIT %u' % length, })[0] if count: return count, a2b_hex(tid_checksum), util.p64(max_tid) return 0, ZERO_HASH, ZERO_TID
def getLastObjectTID(self, oid): oid = util.u64(oid) r = self.query("SELECT tid FROM obj" " WHERE `partition`=%d AND oid=%d" " ORDER BY tid DESC LIMIT 1" % (self._getPartition(oid), oid)) return util.p64(r[0][0]) if r else None
def getLastObjectTID(self, oid): oid = util.u64(oid) r = self.query("SELECT tid FROM obj FORCE INDEX(PRIMARY)" " WHERE `partition`=%d AND oid=%d" " ORDER BY tid DESC LIMIT 1" % (self._getReadablePartition(oid), oid)) return util.p64(r[0][0]) if r else None
def pack(self, tid, updateObjectDataForPack): # TODO: unit test (along with updatePackFuture) p64 = util.p64 tid = util.u64(tid) updatePackFuture = self._updatePackFuture getPartition = self._getReadablePartition q = self.query self._setPackTID(tid) for count, oid, max_serial in q("SELECT COUNT(*) - 1, oid, MAX(tid)" " FROM obj FORCE INDEX(PRIMARY)" " WHERE tid <= %d GROUP BY oid" % tid): partition = getPartition(oid) if q("SELECT 1 FROM obj WHERE `partition` = %d" " AND oid = %d AND tid = %d AND data_id IS NULL" % (partition, oid, max_serial)): max_serial += 1 elif not count: continue # There are things to delete for this object data_id_set = set() sql = ' FROM obj WHERE `partition`=%d AND oid=%d' \ ' AND tid<%d' % (partition, oid, max_serial) for serial, data_id in q('SELECT tid, data_id' + sql): data_id_set.add(data_id) new_serial = updatePackFuture(oid, serial, max_serial) if new_serial: new_serial = p64(new_serial) updateObjectDataForPack(p64(oid), p64(serial), new_serial, data_id) q('DELETE' + sql) data_id_set.discard(None) self._pruneData(data_id_set) self.commit()
def testTruncate(self): calls = [0, 0] def dieFirst(i): def f(orig, *args, **kw): calls[i] += 1 if calls[i] == 1: sys.exit() return orig(*args, **kw) return f cluster = NEOCluster(replicas=1) try: cluster.start() t, c = cluster.getTransaction() r = c.root() tids = [] for x in xrange(4): r[x] = None t.commit() tids.append(r._p_serial) truncate_tid = tids[2] r["x"] = PCounter() s0, s1 = cluster.storage_list with Patch(s0.tm, unlock=dieFirst(0)), Patch(s1.dm, truncate=dieFirst(1)): t.commit() cluster.neoctl.truncate(truncate_tid) self.tic() getClusterState = cluster.neoctl.getClusterState # Unless forced, the cluster waits all nodes to be up, # so that all nodes are truncated. self.assertEqual(getClusterState(), ClusterStates.RECOVERING) self.assertEqual(calls, [1, 0]) s0.resetNode() s0.start() # s0 died with unfinished data, and before processing the # Truncate packet from the master. self.assertFalse(s0.dm.getTruncateTID()) self.assertEqual(s1.dm.getTruncateTID(), truncate_tid) self.tic() self.assertEqual(calls, [1, 1]) self.assertEqual(getClusterState(), ClusterStates.RECOVERING) s1.resetNode() with Patch(s1.dm, truncate=dieFirst(1)): s1.start() self.assertEqual(s0.dm.getLastIDs()[0], truncate_tid) self.assertEqual(s1.dm.getLastIDs()[0], r._p_serial) self.tic() self.assertEqual(calls, [1, 2]) self.assertEqual(getClusterState(), ClusterStates.RUNNING) t.begin() self.assertEqual(r, dict.fromkeys(xrange(3))) self.assertEqual(r._p_serial, truncate_tid) self.assertEqual(1, u64(c._storage.new_oid())) for s in cluster.storage_list: self.assertEqual(s.dm.getLastIDs()[0], truncate_tid) finally: cluster.stop()
def __init__(self, storage, oid=0, **kw): self.oid = int(oid) self.mountpoints = {k: int(v) for k, v in kw.iteritems()} self.connect(storage) self.ltid = util.u64(self.lastTransaction()) if not self.ltid: raise DatabaseFailure("Can not import empty storage: %s" % storage) self.mapping = {}
def getFinalTID(self, ttid): ttid = util.u64(ttid) # As of SQLite 3.8.7.1, 'tid>=ttid' would ignore the index on tid, # even though ttid is a constant. for tid, in self.query("SELECT tid FROM trans" " WHERE partition=? AND tid>=? AND ttid=? LIMIT 1", (self._getPartition(ttid), ttid, ttid)): return util.p64(tid)
def abortTransaction(self, ttid): ttid = util.u64(ttid) q = self.query sql = " FROM tobj WHERE tid=%s" % ttid data_id_list = [x for x, in q("SELECT data_id" + sql) if x] q("DELETE" + sql) q("DELETE FROM ttrans WHERE ttid=%s" % ttid) self.releaseData(data_id_list, True)
def getFinalTID(self, ttid): ttid = util.u64(ttid) # MariaDB is smart enough to realize that 'ttid' is constant. r = self.query("SELECT tid FROM trans" " WHERE `partition`=%s AND tid>=ttid AND ttid=%s LIMIT 1" % (self._getPartition(ttid), ttid)) if r: return util.p64(r[0][0])
def abortTransaction(self, ttid): args = util.u64(ttid), q = self.query sql = " FROM tobj WHERE tid=?" data_id_list = [x for x, in q("SELECT data_id" + sql, args) if x] q("DELETE" + sql, args) q("DELETE FROM ttrans WHERE ttid=?", args) self.releaseData(data_id_list, True)
def getFinalTID(self, ttid): ttid = util.u64(ttid) # As of SQLite 3.8.7.1, 'tid>=ttid' would ignore the index on tid, # even though ttid is a constant. for tid, in self.query("SELECT tid FROM trans" " WHERE partition=? AND tid>=? AND ttid=? LIMIT 1", (self._getReadablePartition(ttid), ttid, ttid)): return util.p64(tid)
def testTruncate(self): calls = [0, 0] def dieFirst(i): def f(orig, *args, **kw): calls[i] += 1 if calls[i] == 1: sys.exit() return orig(*args, **kw) return f cluster = NEOCluster(replicas=1) try: cluster.start() t, c = cluster.getTransaction() r = c.root() tids = [] for x in xrange(4): r[x] = None t.commit() tids.append(r._p_serial) truncate_tid = tids[2] r['x'] = PCounter() s0, s1 = cluster.storage_list with Patch(s0.tm, unlock=dieFirst(0)), \ Patch(s1.dm, truncate=dieFirst(1)): t.commit() cluster.neoctl.truncate(truncate_tid) self.tic() getClusterState = cluster.neoctl.getClusterState # Unless forced, the cluster waits all nodes to be up, # so that all nodes are truncated. self.assertEqual(getClusterState(), ClusterStates.RECOVERING) self.assertEqual(calls, [1, 0]) s0.resetNode() s0.start() # s0 died with unfinished data, and before processing the # Truncate packet from the master. self.assertFalse(s0.dm.getTruncateTID()) self.assertEqual(s1.dm.getTruncateTID(), truncate_tid) self.tic() self.assertEqual(calls, [1, 1]) self.assertEqual(getClusterState(), ClusterStates.RECOVERING) s1.resetNode() with Patch(s1.dm, truncate=dieFirst(1)): s1.start() self.assertEqual(s0.dm.getLastIDs()[0], truncate_tid) self.assertEqual(s1.dm.getLastIDs()[0], r._p_serial) self.tic() self.assertEqual(calls, [1, 2]) self.assertEqual(getClusterState(), ClusterStates.RUNNING) t.begin() self.assertEqual(r, dict.fromkeys(xrange(3))) self.assertEqual(r._p_serial, truncate_tid) self.assertEqual(1, u64(c._storage.new_oid())) for s in cluster.storage_list: self.assertEqual(s.dm.getLastIDs()[0], truncate_tid) finally: cluster.stop()
def getFinalTID(self, ttid): ttid = util.u64(ttid) # MariaDB is smart enough to realize that 'ttid' is constant. r = self.query( "SELECT tid FROM trans" " WHERE `partition`=%s AND tid>=ttid AND ttid=%s LIMIT 1" % (self._getReadablePartition(ttid), ttid)) if r: return util.p64(r[0][0])
def test_findUndoTID(self): self.setNumPartitions(4, True) db = self.db tid1 = self.getNextTID() tid2 = self.getNextTID() tid3 = self.getNextTID() tid4 = self.getNextTID() tid5 = self.getNextTID() oid1 = p64(1) foo = db.holdData("3" * 20, oid1, 'foo', 0) bar = db.holdData("4" * 20, oid1, 'bar', 0) db.releaseData((foo, bar)) db.storeTransaction(tid1, ((oid1, foo, None), ), None, temporary=False) # Undoing oid1 tid1, OK: tid1 is latest # Result: current tid is tid1, data_tid is None (undoing object # creation) self.assertEqual(db.findUndoTID(oid1, tid5, tid4, tid1, None), (tid1, None, True)) # Store a new transaction db.storeTransaction(tid2, ((oid1, bar, None), ), None, temporary=False) # Undoing oid1 tid2, OK: tid2 is latest # Result: current tid is tid2, data_tid is tid1 self.assertEqual(db.findUndoTID(oid1, tid5, tid4, tid2, None), (tid2, tid1, True)) # Undoing oid1 tid1, Error: tid2 is latest # Result: current tid is tid2, data_tid is -1 self.assertEqual(db.findUndoTID(oid1, tid5, tid4, tid1, None), (tid2, None, False)) # Undoing oid1 tid1 with tid2 being undone in same transaction, # OK: tid1 is latest # Result: current tid is tid1, data_tid is None (undoing object # creation) # Explanation of transaction_object: oid1, no data but a data serial # to tid1 self.assertEqual( db.findUndoTID(oid1, tid5, tid4, tid1, (u64(oid1), None, tid1)), (tid1, None, True)) # Store a new transaction db.storeTransaction(tid3, ((oid1, None, tid1), ), None, temporary=False) # Undoing oid1 tid1, OK: tid3 is latest with tid1 data # Result: current tid is tid2, data_tid is None (undoing object # creation) self.assertEqual(db.findUndoTID(oid1, tid5, tid4, tid1, None), (tid3, None, True))
def __init__(self, app): EventQueue.__init__(self) self.read_queue = EventQueue() self._app = app self._transaction_dict = {} self._store_lock_dict = {} self._load_lock_dict = {} self._replicated = {} self._replicating = set() from neo.lib.util import u64 np = app.pt.getPartitions() self.getPartition = lambda oid: u64(oid) % np
def testCommitVsDiscardedCell(self, cluster): s0, s1 = cluster.storage_list cluster.start((s0, )) t, c = cluster.getTransaction() ob = c.root()[''] = PCounterWithResolution() t.commit() self.assertEqual(1, u64(ob._p_oid)) s1.start() self.tic() nonlocal_ = [] with ConnectionFilter() as f: delay = f.delayNotifyReplicationDone() cluster.enableStorageList((s1, )) cluster.neoctl.tweakPartitionTable() self.tic() self.assertPartitionTable(cluster, 'U.|FO') t2, c2 = cluster.getTransaction() c2.root()[''].value += 3 l = threading.Lock() l.acquire() @TransactionalResource(t2, 0) def tpc_vote(_): self.tic() l.release() t2 = self.newPausedThread(t2.commit) @TransactionalResource( t, 0, tpc_finish=lambda _: f.remove(nonlocal_.pop(0))) def tpc_vote(_): t2.start() l.acquire() nonlocal_.append(f.delayNotifyPartitionChanges()) f.remove(delay) self.tic() self.assertPartitionTable(cluster, 'U.|.U', cluster.master) nonlocal_.append(cluster.master.pt.getID()) ob.value += 2 t.commit() t2.join() self.tic() self.assertPartitionTable(cluster, 'U.|.U') self.assertEqual(cluster.master.pt.getID(), nonlocal_.pop()) t.begin() self.assertEqual(ob.value, 5) # get the second to last tid (for which ob=2) tid2 = s1.dm.getObject(ob._p_oid, None, ob._p_serial)[0] # s0 must not have committed anything for partition 1 with s0.dm.replicated(1): self.assertFalse(s0.dm.getObject(ob._p_oid, tid2))
def getTransaction(self, tid, all=False): tid = util.u64(tid) q = self.query r = q("SELECT oids, user, description, ext, packed, ttid" " FROM trans WHERE partition=? AND tid=?", (self._getReadablePartition(tid), tid)).fetchone() if not r and all: r = q("SELECT oids, user, description, ext, packed, ttid" " FROM ttrans WHERE tid=?", (tid,)).fetchone() if r: oids, user, description, ext, packed, ttid = r return splitOIDField(tid, oids), str(user), \ str(description), str(ext), packed, util.p64(ttid)
def _nextTID(self, ttid=None, divisor=None): """ Compute the next TID based on the current time and check collisions. Also, if ttid is not None, divisor is mandatory adjust it so that tid % divisor == ttid % divisor while preserving min_tid < tid If ttid is None, divisor is ignored. When constraints allow, prefer decreasing generated TID, to avoid fast-forwarding to future dates. """ tid = tidFromTime(time()) min_tid = self._last_tid if tid <= min_tid: tid = addTID(min_tid, 1) # We know we won't have room to adjust by decreasing. try_decrease = False else: try_decrease = True if ttid is not None: assert isinstance(ttid, basestring), repr(ttid) assert isinstance(divisor, (int, long)), repr(divisor) ref_remainder = u64(ttid) % divisor remainder = u64(tid) % divisor if ref_remainder != remainder: if try_decrease: new_tid = addTID(tid, ref_remainder - divisor - remainder) assert u64(new_tid) % divisor == ref_remainder, (dump(new_tid), ref_remainder) if new_tid <= min_tid: new_tid = addTID(new_tid, divisor) else: if ref_remainder > remainder: ref_remainder += divisor new_tid = addTID(tid, ref_remainder - remainder) assert min_tid < new_tid, (dump(min_tid), dump(tid), dump(new_tid)) tid = new_tid self._last_tid = tid return self._last_tid
def getTransaction(self, tid, all=False): tid = util.u64(tid) q = self.query r = q("SELECT oids, user, description, ext, packed, ttid" " FROM trans WHERE `partition` = %d AND tid = %d" % (self._getReadablePartition(tid), tid)) if not r and all: r = q("SELECT oids, user, description, ext, packed, ttid" " FROM ttrans WHERE tid = %d" % tid) if r: oids, user, desc, ext, packed, ttid = r[0] oid_list = splitOIDField(tid, oids) return oid_list, user, desc, ext, bool(packed), util.p64(ttid)
def getObjectHistory(self, oid, offset, length): # FIXME: This method doesn't take client's current transaction id as # parameter, which means it can return transactions in the future of # client's transaction. p64 = util.p64 oid = util.u64(oid) return [(p64(tid), length or 0) for tid, length in self.query("""\ SELECT tid, LENGTH(value) FROM obj LEFT JOIN data ON obj.data_id = data.id WHERE partition=? AND oid=? AND tid>=? ORDER BY tid DESC LIMIT ?,?""", (self._getPartition(oid), oid, self._getPackTID(), offset, length)) ] or None
def getObjectHistory(self, oid, offset, length): # FIXME: This method doesn't take client's current transaction id as # parameter, which means it can return transactions in the future of # client's transaction. p64 = util.p64 oid = util.u64(oid) return [(p64(tid), length or 0) for tid, length in self.query( """\ SELECT tid, LENGTH(value) FROM obj LEFT JOIN data ON obj.data_id = data.id WHERE partition=? AND oid=? AND tid>=? ORDER BY tid DESC LIMIT ?,?""", (self._getReadablePartition( oid), oid, self._getPackTID(), offset, length))] or None
def testStorageFailureDuringTpcFinish(self): def answerTransactionFinished(conn, packet): if isinstance(packet, Packets.AnswerTransactionFinished): raise StoppedOperation cluster = NEOCluster() try: cluster.start() t, c = cluster.getTransaction() c.root()['x'] = PCounter() with cluster.master.filterConnection(cluster.client) as m2c: m2c.add(answerTransactionFinished) # After a storage failure during tpc_finish, the client # reconnects and checks that the transaction was really # committed. t.commit() # Also check that the master reset the last oid to a correct value. t.begin() self.assertEqual(1, u64(c.root()['x']._p_oid)) self.assertFalse(cluster.client.new_oid_list) self.assertEqual(2, u64(cluster.client.new_oid())) finally: cluster.stop()
def getTransaction(self, tid, all=False): tid = util.u64(tid) q = self.query r = q("SELECT oids, user, description, ext, packed, ttid" " FROM trans WHERE partition=? AND tid=?", (self._getPartition(tid), tid)).fetchone() if not r and all: r = q("SELECT oids, user, description, ext, packed, ttid" " FROM ttrans WHERE tid=?", (tid,)).fetchone() if r: oids, user, description, ext, packed, ttid = r return splitOIDField(tid, oids), str(user), \ str(description), str(ext), packed, util.p64(ttid)
def getTransaction(self, tid, all = False): tid = util.u64(tid) q = self.query r = q("SELECT oids, user, description, ext, packed, ttid" " FROM trans WHERE `partition` = %d AND tid = %d" % (self._getPartition(tid), tid)) if not r and all: r = q("SELECT oids, user, description, ext, packed, ttid" " FROM ttrans WHERE tid = %d" % tid) if r: oids, user, desc, ext, packed, ttid = r[0] oid_list = splitOIDField(tid, oids) return oid_list, user, desc, ext, bool(packed), util.p64(ttid)
def getObjectHistory(self, oid, offset, length): # FIXME: This method doesn't take client's current ransaction id as # parameter, which means it can return transactions in the future of # client's transaction. oid = util.u64(oid) p64 = util.p64 r = self.query("SELECT tid, IF(compression < 128, LENGTH(value)," " CAST(CONV(HEX(SUBSTR(value, 5, 4)), 16, 10) AS INT))" " FROM obj LEFT JOIN data ON (obj.data_id = data.id)" " WHERE `partition` = %d AND oid = %d AND tid >= %d" " ORDER BY tid DESC LIMIT %d, %d" % (self._getPartition(oid), oid, self._getPackTID(), offset, length)) if r: return [(p64(tid), length or 0) for tid, length in r]
def setup(self, zodb_dict, shift_oid=0): self.shift_oid = shift_oid self.next_oid = util.u64(self.new_oid()) shift_oid += self.next_oid for mp, oid in self.mountpoints.iteritems(): mp = zodb_dict[mp] new_oid = mp.oid try: new_oid += mp.shift_oid except AttributeError: new_oid += shift_oid shift_oid = mp.setup(zodb_dict, shift_oid) self.mapping[oid] = new_oid del self.mountpoints return shift_oid
def __str__(self): oid = u64(self.oid) tid = u64(self.tid) args = (oid, tid, len(self.data), self.data_txn) return 'Record %s:%s: %s (%s)' % args
def deleteTransaction(self, tid): tid = util.u64(tid) getPartition = self._getPartition self.query("DELETE FROM trans WHERE `partition`=%s AND tid=%s" % (self._getPartition(tid), tid))
def testVerificationCommitUnfinishedTransactions(self): """ Verification step should commit locked transactions """ def delayUnlockInformation(conn, packet): return isinstance(packet, Packets.NotifyUnlockInformation) def onLockTransaction(storage, die=False): def lock(orig, *args, **kw): if die: sys.exit() orig(*args, **kw) storage.master_conn.close() return Patch(storage.tm, lock=lock) cluster = NEOCluster(partitions=2, storage_count=2) try: cluster.start() s0, s1 = cluster.sortStorageList() t, c = cluster.getTransaction() r = c.root() r[0] = PCounter() tids = [r._p_serial] with onLockTransaction(s0), onLockTransaction(s1): t.commit() self.assertEqual(r._p_state, GHOST) self.tic() t.begin() x = r[0] self.assertEqual(x.value, 0) cluster.master.tm._last_oid = x._p_oid tids.append(r._p_serial) r[1] = PCounter() c.readCurrent(x) with cluster.moduloTID(1): with onLockTransaction(s0), onLockTransaction(s1): t.commit() self.tic() t.begin() # The following line checks that s1 moved the transaction # metadata to final place during the verification phase. # If it didn't, a NEOStorageError would be raised. self.assertEqual(3, len(c.db().history(r._p_oid, 4))) y = r[1] self.assertEqual(y.value, 0) self.assertEqual([u64(o._p_oid) for o in (r, x, y)], range(3)) r[2] = 'ok' with cluster.master.filterConnection(s0) as m2s: m2s.add(delayUnlockInformation) t.commit() x.value = 1 # s0 will accept to store y (because it's not locked) but will # never lock the transaction (packets from master delayed), # so the last transaction will be dropped. y.value = 2 di0 = s0.getDataLockInfo() with onLockTransaction(s1, die=True): self.commitWithStorageFailure(cluster.client, t) finally: cluster.stop() cluster.reset() (k, v), = set(s0.getDataLockInfo().iteritems() ).difference(di0.iteritems()) self.assertEqual(v, 1) k, = (k for k, v in di0.iteritems() if v == 1) di0[k] = 0 # r[2] = 'ok' self.assertEqual(di0.values(), [0, 0, 0, 0, 0]) di1 = s1.getDataLockInfo() k, = (k for k, v in di1.iteritems() if v == 1) del di1[k] # x.value = 1 self.assertEqual(di1.values(), [0]) try: cluster.start() t, c = cluster.getTransaction() r = c.root() self.assertEqual(r[0].value, 0) self.assertEqual(r[1].value, 0) self.assertEqual(r[2], 'ok') self.assertEqual(di0, s0.getDataLockInfo()) self.assertEqual(di1, s1.getDataLockInfo()) finally: cluster.stop()
def load(self, oid, tid=None, before_tid=None): """ Internal method which manage load, loadSerial and loadBefore. OID and TID (serial) parameters are expected packed. oid OID of object to get. tid If given, the exact serial at which OID is desired. before_tid should be None. before_tid If given, the excluded upper bound serial at which OID is desired. serial should be None. Return value: (3-tuple) - Object data (None if object creation was undone). - Serial of given data. - Next serial at which object exists, or None. Only set when tid parameter is not None. Exceptions: NEOStorageError technical problem NEOStorageNotFoundError object exists but no data satisfies given parameters NEOStorageDoesNotExistError object doesn't exist NEOStorageCreationUndoneError object existed, but its creation was undone Note that loadSerial is used during conflict resolution to load object's current version, which is not visible to us normaly (it was committed after our snapshot was taken). """ # TODO: # - rename parameters (here? and in handlers & packet definitions) # When not bound to a ZODB Connection, load() may be the # first method called and last_tid may still be None. # This happens, for example, when opening the DB. if not (tid or before_tid) and self.last_tid: # Do not get something more recent than the last invalidation # we got from master. before_tid = p64(u64(self.last_tid) + 1) acquire = self._cache_lock_acquire release = self._cache_lock_release # XXX: Consider using a more fine-grained lock. self._load_lock_acquire() try: acquire() try: result = self._loadFromCache(oid, tid, before_tid) if result: return result self._loading_oid = oid finally: release() data, tid, next_tid, _ = self._loadFromStorage(oid, tid, before_tid) acquire() try: if self._loading_oid: # Common case (no race condition). self._cache.store(oid, data, tid, next_tid) elif self._loading_invalidated: # oid has just been invalidated. if not next_tid: next_tid = self._loading_invalidated self._cache.store(oid, data, tid, next_tid) # Else, we just reconnected to the master. finally: release() finally: self._load_lock_release() return data, tid, next_tid
def undo(self, undone_tid, txn, tryToResolveConflict): txn_context = self._txn_container.get(txn) txn_info, txn_ext = self._getTransactionInformation(undone_tid) txn_oid_list = txn_info['oids'] # Regroup objects per partition, to ask a minimum set of storage. partition_oid_dict = {} for oid in txn_oid_list: partition = self.pt.getPartition(oid) try: oid_list = partition_oid_dict[partition] except KeyError: oid_list = partition_oid_dict[partition] = [] oid_list.append(oid) # Ask storage the undo serial (serial at which object's previous data # is) getCellList = self.pt.getCellList getCellSortKey = self.cp.getCellSortKey getConnForCell = self.cp.getConnForCell queue = self._thread_container.queue ttid = txn_context['ttid'] undo_object_tid_dict = {} snapshot_tid = p64(u64(self.last_tid) + 1) for partition, oid_list in partition_oid_dict.iteritems(): cell_list = getCellList(partition, readable=True) # We do want to shuffle before getting one with the smallest # key, so that all cells with the same (smallest) key has # identical chance to be chosen. shuffle(cell_list) storage_conn = getConnForCell(min(cell_list, key=getCellSortKey)) storage_conn.ask(Packets.AskObjectUndoSerial(ttid, snapshot_tid, undone_tid, oid_list), queue=queue, undo_object_tid_dict=undo_object_tid_dict) # Wait for all AnswerObjectUndoSerial. We might get OidNotFoundError, # meaning that objects in transaction's oid_list do not exist any # longer. This is the symptom of a pack, so forbid undoing transaction # when it happens. try: self.waitResponses(queue) except NEOStorageNotFoundError: self.dispatcher.forget_queue(queue) raise UndoError('non-undoable transaction') # Send undo data to all storage nodes. for oid in txn_oid_list: current_serial, undo_serial, is_current = undo_object_tid_dict[oid] if is_current: data = None else: # Serial being undone is not the latest version for this # object. This is an undo conflict, try to resolve it. try: # Load the latest version we are supposed to see data = self.load(oid, current_serial)[0] # Load the version we were undoing to undo_data = self.load(oid, undo_serial)[0] except NEOStorageNotFoundError: raise UndoError('Object not found while resolving undo ' 'conflict') # Resolve conflict try: data = tryToResolveConflict(oid, current_serial, undone_tid, undo_data, data) except ConflictError: raise UndoError('Some data were modified by a later ' \ 'transaction', oid) undo_serial = None self._store(txn_context, oid, current_serial, data, undo_serial) return None, txn_oid_list
def test_findUndoTID(self): self.setNumPartitions(4, True) db = self.db tid1 = self.getNextTID() tid2 = self.getNextTID() tid3 = self.getNextTID() tid4 = self.getNextTID() tid5 = self.getNextTID() oid1 = self.getOID(1) foo = db.holdData("3" * 20, 'foo', 0) bar = db.holdData("4" * 20, 'bar', 0) db.releaseData((foo, bar)) db.storeTransaction( tid1, ( (oid1, foo, None), ), None, temporary=False) # Undoing oid1 tid1, OK: tid1 is latest # Result: current tid is tid1, data_tid is None (undoing object # creation) self.assertEqual( db.findUndoTID(oid1, tid5, tid4, tid1, None), (tid1, None, True)) # Store a new transaction db.storeTransaction( tid2, ( (oid1, bar, None), ), None, temporary=False) # Undoing oid1 tid2, OK: tid2 is latest # Result: current tid is tid2, data_tid is tid1 self.assertEqual( db.findUndoTID(oid1, tid5, tid4, tid2, None), (tid2, tid1, True)) # Undoing oid1 tid1, Error: tid2 is latest # Result: current tid is tid2, data_tid is -1 self.assertEqual( db.findUndoTID(oid1, tid5, tid4, tid1, None), (tid2, None, False)) # Undoing oid1 tid1 with tid2 being undone in same transaction, # OK: tid1 is latest # Result: current tid is tid1, data_tid is None (undoing object # creation) # Explanation of transaction_object: oid1, no data but a data serial # to tid1 self.assertEqual( db.findUndoTID(oid1, tid5, tid4, tid1, (u64(oid1), None, tid1)), (tid1, None, True)) # Store a new transaction db.storeTransaction( tid3, ( (oid1, None, tid1), ), None, temporary=False) # Undoing oid1 tid1, OK: tid3 is latest with tid1 data # Result: current tid is tid2, data_tid is None (undoing object # creation) self.assertEqual( db.findUndoTID(oid1, tid5, tid4, tid1, None), (tid3, None, True))
def __str__(self): return 'Transaction #%s: %s %s' \ % (u64(self.tid), self.user, self.status)
def __dump(self, storage): return {u64(t.tid): [(u64(o.oid), o.data_txn and u64(o.data_txn), None if o.data is None else makeChecksum(o.data)) for o in t] for t in storage.iterator()}