def test_user_abort(self): t = ZKTransaction(zkhost) txid = None try: with ZKTransaction(zkhost) as t1: txid = t1.txid t1.set_mem_state('bar') self.assertIsNone(t.zkstorage.state.get(txid)[0]) val = t1.mem_state self.assertEqual( { 'got_keys': [], 'data': 'bar', "start_ts": t1.start_ts }, val) val = t1.get_mem_state() self.assertEqual('bar', val) t1.abort() except UserAborted: pass self.assertIsNone(t.zkstorage.state.get(txid)[0])
def test_lock_get_timeout(self): def _tx(tx): tx.begin() tx.lock_get('foo') time.sleep(4) th = threadutil.start_daemon(_tx, args=(ZKTransaction(zkhost, txid=0), )) with ZKTransaction(zkhost, lock_timeout=0.5) as t1: try: t1.lock_get('foo') self.fail('TXTimeout expected') except TXTimeout as e: dd(repr(e)) with ZKTransaction(zkhost) as t2: try: t2.lock_get('foo', timeout=0.5) self.fail('TXTimeout expected') except TXTimeout as e: dd(repr(e)) th.join()
def test_journal_purge(self): n_tx = 10 k = 'foo' for ii in range(n_tx): with ZKTransaction(zkhost) as t1: t1.zkstorage.max_journal_history = 5 foo = t1.lock_get(k) foo.v = foo.v or 0 foo.v += 1 t1.set(foo) t1.commit() t = ZKTransaction(zkhost) journal_id_set, ver = t.zkstorage.journal_id_set.get() self.assertEqual({ PURGED: [[0, 5]], COMMITTED: [[0, 10]] }, journal_id_set) for i in range(5): self.assertRaises(NoNodeError, t.zkstorage.journal.get, '%010d' % i)
def test_committed(self): t = ZKTransaction(zkhost) txid = None with ZKTransaction(zkhost) as t1: txid = t1.txid # not set yet self.assertIsNone(t.zkstorage.state.get(txid)[0]) foo = t1.lock_get('foo') foo.v = 100 t1.set(foo) t1.set_state('bar') val, ver = t.zkstorage.state.get(txid) self.assertEqual( { 'got_keys': ['foo'], 'data': 'bar', "start_ts": t1.start_ts }, val) t1.commit() self.assertIsNone(t.zkstorage.state.get(txid)[0])
def test_exception_with_state(self): t = ZKTransaction(zkhost) txid = None try: with ZKTransaction(zkhost) as t1: t1.lock_get('foo') raise ValueError('foo') except ValueError: pass nodes = t.zke.get_children('/lock') self.assertEqual([], nodes) try: with ZKTransaction(zkhost) as t2: txid = t2.txid t2.lock_get('foo') t2.set_state('bar') raise ValueError('foo') except ValueError: pass val, ver = t.zkstorage.state.get(txid) self.assertEqual({'got_keys': ['foo'], 'data': 'bar'}, val) nodes = t.zke.get_children('/lock') self.assertEqual(['foo'], nodes)
def test_exception(self): # tx raised by other exception is recoverable t = ZKTransaction(zkhost) txid = None try: with ZKTransaction(zkhost) as t1: txid = t1.txid t1.set_state('bar') start_ts = t1.start_ts val, ver = t.zkstorage.state.get(txid) self.assertEqual( { 'got_keys': [], 'data': 'bar', "start_ts": t1.start_ts }, val) raise ValueError('foo') except ValueError: pass val, ver = t.zkstorage.state.get(txid) self.assertEqual({ 'got_keys': [], 'data': 'bar', "start_ts": start_ts }, val)
def test_recover(self): txid = None with ZKTransaction(zkhost, timeout=0.5) as t1: txid = t1.txid f1 = t1.lock_get('foo') f2 = t1.lock_get('foo2') f1.v = "foo_val" f2.v = "foo2_val" t1.set(f1) t1.set(f2) t1.set_mem_state('bar') with ZKTransaction(zkhost, txid=txid, timeout=0.5) as t2: st = t2.get_state() self.assertEqual(None, st) f1 = t2.lock_get('foo') f2 = t2.lock_get('foo2') self.assertEqual(f1.v, None) self.assertEqual(f2.v, None) try: with ZKTransaction(zkhost, txid=txid, timeout=0.5) as t3: dd(t3) self.fail("expected TXTimeout") except TXTimeout: pass
def test_sequential(self): n_tx = 10 k = 'foo' for ii in range(n_tx): with ZKTransaction(zkhost) as t1: foo = t1.lock_get(k) foo.v = foo.v or 0 foo.v += 1 t1.set(foo) t1.commit() t = ZKTransaction(zkhost) rst, ver = t.zkstorage.record.get(k) dd(rst) self.assertEqual(n_tx, rst[-1]) rst, ver = t.zkstorage.journal_id_set.get() dd(rst) self.assertEqual(n_tx, rst[COMMITTED].length())
def test_journal_purge(self): n_tx = 10 k = 'foo' # ACACACACACACACACACAC # 01234567890123456789 for ii in range(n_tx): with ZKTransaction(zkhost) as t1: t1.zkstorage.max_journal_history = 5 foo = t1.lock_get(k) foo.v = 1 t1.set(foo) t1.abort() t = ZKTransaction(zkhost) txidset, ver = t.zkstorage.txidset.get() self.assertLessEqual(txidset[COMMITTED].length(), 5) with ZKTransaction(zkhost) as t1: t1.zkstorage.max_journal_history = 5 foo = t1.lock_get(k) foo.v = 1 t1.set(foo) t1.commit() t = ZKTransaction(zkhost) txidset, ver = t.zkstorage.txidset.get() self.assertLessEqual(txidset[COMMITTED].length(), 5)
def test_redo_dead_tx_without_journal(self): t1 = ZKTransaction(zkhost) t1.begin() t1.lock_get('foo') t1.zke.stop() with ZKTransaction(zkhost) as t2: foo = t2.lock_get('foo') foo.v = foo.v or 0 foo.v += 1 t2.set(foo) t2.commit() t = ZKTransaction(zkhost) rst, ver = t.zkstorage.record.get('foo') dd(rst) self.assertEqual(1, rst[-1]) journal_id_set, ver = t.zkstorage.journal_id_set.get() self.assertEqual({COMMITTED: [[0, 1]], PURGED: []}, journal_id_set)
def test_deepcopy_value(self): with ZKTransaction(zkhost) as t1: foo = t1.lock_get('foo') foo.v = {'foo': 'foo'} t1.set(foo) t1.commit() with ZKTransaction(zkhost) as t1: # When retrieving a dict, it should be deep-copied. # All zktx always though modified == original foo = t1.lock_get('foo') foo.v['foo'] = 'bar' t1.set(foo) t1.commit() rst, ver = self.zk.get('record/foo') self.assertEqual([{ 'foo': 'foo' }, { 'foo': 'bar' }], utfjson.load(rst)[-2:])
def test_exception_and_recover(self): # tx raised by other exception is recoverable t = ZKTransaction(zkhost) txid = None try: with ZKTransaction(zkhost) as t1: start_ts = t1.start_ts txid = t1.txid f1 = t1.lock_get('foox') f1.v = "foox_val" t1.set(f1) t1.set_state('bar') start_ts = t1.start_ts val, ver = t.zkstorage.state.get(txid) self.assertEqual( { 'got_keys': ["foox"], 'data': 'bar', "start_ts": start_ts }, val) raise ValueError('foo') except ValueError: pass val, ver = t.zkstorage.state.get(txid) self.assertEqual( { 'got_keys': ['foox'], 'data': 'bar', "start_ts": start_ts }, val) ident = zkutil.make_identifier(txid, None) keylock = zkutil.ZKLock('foox', zkclient=t.zke, zkconf=t.zke._zkconf, ephemeral=False, identifier=ident) val, ver = keylock.get_lock_val() self.assertEqual(val['v'], "foox_val") with ZKTransaction(zkhost, txid=txid, timeout=1) as t1: self.assertEqual(t1.start_ts, start_ts) f1 = t1.lock_get('foox') self.assertEqual(f1.v, "foox_val") val = t1.get_state() self.assertEqual(val, 'bar')
def test_redo_all(self): # txid=1 committed # txid=2 nothing # txid=3 has journal # txid=4 no tx but has state # txid=5 committed # txid=1 with ZKTransaction(zkhost) as t1: foo = t1.lock_get('foo') foo.v = 1 t1.set(foo) t1.commit() t = ZKTransaction(zkhost) # make txid=2, leave it a hole t.zke.set(t.zke._zkconf.txid_maker(), 'x') # make txid=3 t.zke.set(t.zke._zkconf.txid_maker(), 'x') txid = 3 # fake a journal t.zkstorage.journal.create(txid, {'bar': 3}) # txid=4, has state t.zke.set(t.zke._zkconf.txid_maker(), 'x') t.zkstorage.state.create(4, {'xx': 'yy'}) # txid=5, committed with ZKTransaction(zkhost) as t5: foo = t5.lock_get('foo') foo.v = 5 t5.set(foo) t5.commit() sets, ver = t.zkstorage.txidset.get() dd('init txidset:', sets) self.assertEqual({'PURGED': [], 'COMMITTED': [[1, 2], [5, 6]]}, sets) t.redo_all_dead_tx() # redo txid=2, abort sets, ver = t.zkstorage.txidset.get() dd('after redo 2:', sets) self.assertEqual({'PURGED': [[2, 3]], 'COMMITTED': [[1, 2], [5, 6]]}, sets) t.redo_all_dead_tx() # redo txid=3, committed sets, ver = t.zkstorage.txidset.get() dd('after redo 3:', sets) self.assertEqual({'PURGED': [[2, 3]], 'COMMITTED': [[1, 2], [3, 4], [5, 6]]}, sets) t.redo_all_dead_tx() # redo txid=4, can not redo a tx with state sets, ver = t.zkstorage.txidset.get() dd('after redo 4, ignored 4:', sets) self.assertEqual({'PURGED': [[2, 3]], 'COMMITTED': [[1, 2], [3, 4], [5, 6]]}, sets)
def test_deadlock(self): # deadlock is not recoverable t = ZKTransaction(zkhost) # t.txid is lower t.begin() t.lock_get('foo') def _commit(): t.commit() txid = None try: with ZKTransaction(zkhost, timeout=0.5) as t1: txid = t1.txid # lock another key first to produce deadlock t1.lock_get('woo') t1.set_state('bar') threadutil.start_daemon(_commit, after=0.2) t1.lock_get('foo') # should deadlock waiting for higher txid except Deadlock: pass t = ZKTransaction(zkhost) self.assertIsNone(t.zkstorage.state.get(txid)[0])
def test_recover_and_timeout(self): # tx raised by other exception is recoverable t = ZKTransaction(zkhost) txid = None try: with ZKTransaction(zkhost) as t1: txid = t1.txid f1 = t1.lock_get('foox') f1.v = "foox_val" t1.set(f1) t1.set_state('bar') start_ts = t1.start_ts val, ver = t.zkstorage.state.get(txid) self.assertEqual( { 'got_keys': ["foox"], 'data': 'bar', "start_ts": t1.start_ts }, val) raise ValueError('foo') except ValueError: pass val, ver = t.zkstorage.state.get(txid) self.assertEqual( { 'got_keys': ["foox"], 'data': 'bar', "start_ts": start_ts }, val) time.sleep(2) try: with ZKTransaction(zkhost, txid=txid, timeout=3) as t1: self.assertEqual(t1.start_ts, start_ts) f1 = t1.lock_get('foox') self.assertEqual(f1.v, "foox_val") val = t1.get_state() self.assertEqual(val, 'bar') except TXTimeout: pass
def test_list_recoverable(self): with ZKTransaction(zkhost, timeout=0.5) as t1: t1.lock_get('foo') t1.set_mem_state('bar') with ZKTransaction(zkhost, timeout=0.5) as t2: t2.lock_get('foo2') t2.set_mem_state('bar2') self.assertEqual([], [x for x in zktx.list_recoverable(zkhost)])
def test_unlock_with_state(self): # NotLocked is retrieable txid = None try: with ZKTransaction(zkhost, timeout=0.5) as t1: txid = t1.txid t1.set_state('bar') start_ts = t1.start_ts foo = t1.lock_get('foo') t1.unlock(foo) t1.unlock(foo) except NotLocked: pass t = ZKTransaction(zkhost) val, ver = t.zkstorage.state.get(txid) self.assertEqual({ 'got_keys': [], 'data': 'bar', "start_ts": start_ts }, val) # UnlockNotAllowed is retrieable txid = None try: with ZKTransaction(zkhost, timeout=0.5) as t1: txid = t1.txid t1.set_state('bar') start_ts = t1.start_ts foo = t1.lock_get('foo') t1.set(foo) t1.unlock(foo) except UnlockNotAllowed: pass t = ZKTransaction(zkhost) val, ver = t.zkstorage.state.get(txid) self.assertEqual({ 'got_keys': [], 'data': 'bar', "start_ts": start_ts }, val)
def test_noblocking_lock_get(self): with ZKTransaction(zkhost) as t1: t1.lock_get('foo') f2 = t1.lock_get('foo', blocking=False) self.assertIsNotNone(f2) with ZKTransaction(zkhost) as t2: f4 = t2.lock_get('foo', blocking=False) self.assertIsNone(f4)
def test_redo_dead_tx_without_journal(self): t1 = ZKTransaction(zkhost) t1.begin() t1.lock_get('foo') t1.zke.stop() with ZKTransaction(zkhost) as t2: foo = t2.lock_get('foo') foo.v = foo.v or 0 foo.v += 1 t2.set(foo) t2.commit() t = ZKTransaction(zkhost) rst, ver = t.zkstorage.record.get('foo') dd(rst) self.assertEqual(1, rst[-1][1]) rst, ver = t.zkstorage.txidset.get() dd(rst) self.assertEqual([1, 2], rst[PURGED][0]) self.assertEqual([2, 3], rst[COMMITTED][0])
def test_uncommitted(self): # uncommitted is recoverable t = ZKTransaction(zkhost) txid = None with ZKTransaction(zkhost, timeout=0.5) as t1: txid = t1.txid t1.lock_get('foo') t1.set_state('bar') val, ver = t.zkstorage.state.get(txid) self.assertEqual({'got_keys': ['foo'], 'data': 'bar'}, val)
def test_list_recoverable(self): txid = None with ZKTransaction(zkhost, timeout=0.5) as t1: txid = t1.txid t1.lock_get('foo') t1.set_state('bar') with ZKTransaction(zkhost, timeout=0.5) as t2: txid = t2.txid t2.lock_get('foo2') t2.set_state('bar2') self.assertEqual([(txid, 'bar2')], [x for x in zktx.list_recoverable(zkhost)])
def _tx(i): while True: try: with ZKTransaction(zkhost) as t1: for ii in range(len(ks)): k = ks[(ii+i) % len(ks)] foo = t1.lock_get(k) foo.v = foo.v or 0 foo.v += 1 t1.set(foo) t1.commit() dd(str(t1) + ' committed') return except (Deadlock, HigherTXApplied) as e: dd(str(t1) + ': ' + repr(e)) continue except TXTimeout as e: dd(str(t1) + ': ' + repr(e)) raise
def test_abort(self): try: with ZKTransaction(zkhost) as t1: foo = t1.lock_get('foo') foo.v = 100 t1.set(foo) t1.abort() except UserAborted: pass t = ZKTransaction(zkhost) rst, ver = t.zkstorage.record.get('foo') dd(rst) self.assertEqual(None, rst[-1])
def test_concurrent_single_record(self): n_tx = 10 def _tx(): while True: try: with ZKTransaction(zkhost) as t1: foo = t1.lock_get('foo') foo.v = foo.v or 0 foo.v += 1 t1.set(foo) t1.commit() return except Deadlock as e: dd(repr(e)) continue for th in [threadutil.start_daemon(_tx) for i in range(n_tx)]: th.join() t = ZKTransaction(zkhost) rst, ver = t.zkstorage.record.get('foo') dd(rst) self.assertEqual(n_tx, rst[-1]) rst, ver = t.zkstorage.journal_id_set.get() dd(rst) self.assertEqual(n_tx, rst[COMMITTED].length())
def test_user_abort(self): t = ZKTransaction(zkhost) txid = None with ZKTransaction(zkhost) as t1: txid = t1.txid t1.set_state('bar') val, ver = t.zkstorage.state.get(txid) self.assertEqual({'got_keys': [], 'data': 'bar'}, val) t1.abort() self.assertIsNone(t.zkstorage.state.get(txid)[0])
def test_run_tx_unretriable_error(self): sess = dict(n=0) errs = [UserAborted, TXTimeout, ConnectionLoss, None] def _tx(tx, err): sess['n'] += 1 foo = tx.lock_get('foo') foo.v = 100 tx.set(foo) if err is not None: raise err() else: tx.commit() for err in errs: try: zktx.run_tx(zkhost, _tx, args=(err, )) except TXError as e: dd(repr(e)) self.assertEqual(len(errs), sess['n']) tx = ZKTransaction(zkhost) rst, ver = tx.zkstorage.record.get('foo') self.assertEqual(100, rst[-1])
def test_unlock_changed_record(self): with ZKTransaction(zkhost) as t1: foo = t1.lock_get('foo') t1.set(foo) self.assertRaises(UnlockNotAllowed, t1.unlock, foo)
def test_unlock_nonlocked(self): with ZKTransaction(zkhost) as t1: foo = t1.lock_get('foo') t1.unlock(foo) self.assertRaises(NotLocked, t1.unlock, foo)
def test_unlock(self): with ZKTransaction(zkhost) as t1: foo = t1.lock_get('foo') t1.unlock(foo) self.assertEqual({}, t1.got_keys) # re-get is ok foo = t1.lock_get('foo') t1.unlock(foo) with ZKTransaction(zkhost) as t2: # t2 can lock a unlocked key t2.lock_get('foo')
def test_abort(self): with ZKTransaction(zkhost) as t1: foo = t1.lock_get('foo') foo.v = 100 t1.set(foo) t1.abort() t = ZKTransaction(zkhost) rst, ver = t.zkstorage.record.get('foo') dd(rst) self.assertEqual(None, rst[-1][1]) rst, ver = t.zkstorage.txidset.get() dd(rst) self.assertEqual([1, 2], rst[PURGED][0])
def test_timeout(self): # timeout on waiting is recoverable t = ZKTransaction(zkhost) txid = None try: with ZKTransaction(zkhost, timeout=0.5) as t1: txid = t1.txid # t.txid is higher t.begin() t.lock_get('foo') t1.set_state('bar') t1.lock_get('foo') # should timeout waiting for higher txid except TXTimeout: pass val, ver = t.zkstorage.state.get(txid) self.assertEqual({'got_keys': [], 'data': 'bar'}, val)
def test_single_record(self): tx = ZKTransaction(zkhost) tx.begin() foo = tx.lock_get('foo') self.assertEqual('foo', foo.k) self.assertIsNone(foo.v) foo.v = 1 tx.set(foo) tx.commit() rst, ver = self.zk.get('record/foo') self.assertEqual([None, 1], utfjson.load(rst)) rst, ver = self.zk.get('tx/journal_id_set') self.assertEqual({COMMITTED: [[0, 1]], PURGED: [], }, utfjson.load(rst))
def test_noblocking_lock_get(self): with ZKTransaction(zkhost) as t1: t1.lock_get('foo') f2 = t1.lock_get('foo', blocking=False) self.assertIsNotNone(f2) with ZKTransaction(zkhost) as t2: f4 = t2.lock_get('foo', blocking=False) self.assertIsNone(f4) t = ZKTransaction(zkhost) t.begin() t.lock_get('foo') t.zke.stop() dd(self.zk.get('lock/foo')) # can lock the key which is hold by a dead tx without state with ZKTransaction(zkhost) as t1: self.assertIsNotNone(t1.lock_get('foo', blocking=False)) t = ZKTransaction(zkhost) t.begin() t.lock_get('foo') t.set_state('s') t.zke.stop() dd(self.zk.get('lock/foo')) # can not lock the key which is hold by a dead tx with state with ZKTransaction(zkhost) as t1: self.assertIsNone(t1.lock_get('foo', blocking=False))