def t2(): t1launched.wait() # This thread does the committing after t1 started but before it reads the value of refA self.d("** Creating ref") self.refA = Ref(5, None) self.refA.refSet(6)
def testBarge(self): # Barging happens when one transaction tries to do an in-transaction-write to a ref that has # an in-transaction-value from another transaction t1wait = Event() t2wait = Event() self.t1counter = 0 self.t2counter = 0 def t1(): # We do the first in-transaction-write self.refA.refSet(888) # Don't commit yet, we want t2 to run and barge us self.d("* Notify") t2wait.set() self.d("* Wait") t1wait.wait() self.d("* Done") self.t1counter += 1 def t2(): # Wait till t1 has done its write self.d("** Wait") t2wait.wait() # Try to write to the ref # We should try and succeed to barge them: we were started first # and should be long-lived enough self.d("** Before barge") self.refA.refSet(777) self.d("** After barge") sleep(.1) t1wait.set() self.t2counter += 1 self.refA = Ref(999, None) th1 = self.runTransactionInThread(t1, autostart=False) th2 = self.runTransactionInThread(t2, autostart=False) # Start thread 1 first, give the GIL a cycle so it waits for t2wait th2.start() sleep(.1) th1.start() # Wait for the test to finish self.join_all() # T2 should have successfully barged T1, so T1 should have been re-run # The final value of the ref should be 888, as T1 ran last self.assertEqual(self.t1counter, 2) self.assertEqual(self.t2counter, 1) self.assertEqual(self.refA.deref(), 888) self.d("Final value of ref: %s and t1 ran %s times" % (self.refA.deref(), self.t1counter))
def testSimpleConcurrency(self): def t1(): sleep(.1) self.ref0.refSet(1) def t2(): self.ref0.refSet(2) # Delaying t1 means it should commit after t2 self.ref0 = Ref(0, None) self.runTransactionInThread(t1) self.runTransactionInThread(t2) self.join_all() self.assertEqual(self.ref0.deref(), 1)
class TestRef(unittest.TestCase): def setUp(self): self.refZero = Ref(0, None) self.refOne = Ref(pv.vec(range(10)), None) ### Internal state def testInternalState_PASS(self): ## NOTE depends on number of test cases, ugh # self.assertEqual(self.refZero._id, 22) # self.assertEqual(self.refOne._id, 23) self.assertEqual(self.refZero._faults.get(), 0) self.assertEqual(self.refOne._faults.get(), 0) self.assertIsNone(self.refZero._tinfo) self.assertIsInstance(self.refZero._lock, SharedLock) self.assertIsInstance(self.refZero._tvals, TVal) def testTVal_PASS(self): self.assertEqual(self.refZero._tvals.val, 0) self.assertEqual(self.refZero._tvals.point, 0) self.assertGreater(self.refZero._tvals.msecs, 0) self.assertEqual(self.refZero._tvals.next, self.refZero._tvals) self.assertEqual(self.refZero._tvals.prev, self.refZero._tvals) ### External API def testEquality_PASS(self): self.assertEqual(self.refZero, self.refZero) def testCurrentValPASS(self): self.assertEqual(self.refZero._currentVal(), 0) def testDeref_PASS(self): self.assertEqual(self.refZero.deref(), 0) def testDerefVec_PASS(self): self.assertEqual(self.refOne.deref(), pv.vec(range(10))) def testSetNoTransaction_FAIL(self): self.assertRaises(IllegalStateException, self.refOne.refSet, 1) def testAlterNoTransaction_FAIL(self): self.assertRaises(IllegalStateException, self.refOne.alter, lambda x: x**2, []) def testCommuteNoTransaction_FAIL(self): self.assertRaises(IllegalStateException, self.refOne.commute, lambda x: x**2, []) def testTouchNoTransaction_FAIL(self): self.assertRaises(IllegalStateException, self.refOne.touch) def testBound_PASS(self): self.assertTrue(self.refOne.isBound()) def testHistoryLen_PASS(self): self.assertEqual(self.refOne.historyCount(), 1) def testTrimHistory_PASS(self): self.refOne.trimHistory() self.assertEqual(self.refOne.historyCount(), 1)
def testCommutes(self): # Make sure multiple transactions that occur simultaneously each commute the same ref # Hard to check for this behaving properly---it should have fewer retries than if each # transaction did an alter, but if transactions commit at the same time one might have to retry # anyway. The difference is usually an order of magnitude, so this test is pretty safe self.numruns = AtomicInteger() self.numalterruns = AtomicInteger() numthreads = 20 def incr(curval): return curval + 1 def adder(curval, extraval): return curval + extraval def t1(): self.numruns.getAndIncrement() self.refA.commute(incr, []) def t2(): # self.d("Thread %s (%s): ALTER BEING RUN, total retry num: %s" % (current_thread().ident, id(current_thread()), self.numalterruns.getAndIncrement())) self.numalterruns.getAndIncrement() self.refB.alter(adder, [100]) self.refA = Ref(0, None) self.refB = Ref(0, None) for i in range(numthreads): self.runTransactionInThread(t1) self.join_all() for i in range(numthreads): self.runTransactionInThread(t2) self.join_all() self.d("Commute took %s runs and counter is %s" % (self.numruns.get(), self.refA.deref())) self.d("Alter took %s runs and counter is %s" % (self.numalterruns.get(), self.refB.deref())) self.assertEqual(self.refA.deref(), numthreads) self.assertEqual(self.refB.deref(), 2000) self.assertTrue(self.numalterruns.get() >= self.numruns.get())
def testRun_PASS(self): # Now we'll run a transaction ourselves self.refOne = Ref(111, None) self.refTwo = Ref(222, None) # Our transaction body will do a ref-set and an alter (increment a ref by 1) def body(): # Body of the transaction! self.refZero.refSet(999) def incr(val): return val + 1 self.refOne.alter(incr, []) # Test our transaction actually made the changes it should have LockingTransaction.runInTransaction(body) self.assertEqual(self.refZero.deref(), 999) self.assertEqual(self.refOne.deref(), 112) self.assertEqual(self.refTwo.deref(), 222) # Test that the transaction ended properly self.assertRaises(IllegalStateException, self.refZero.refSet, 999)
class TestThreadedTransactions(unittest.TestCase): spawned_threads = [] def setUp(self): self.main_thread = current_thread() self.first_run = local() self.first_run.data = True def tearDown(self): # Join all if not joined with yet self.join_all() def d(self, str): """ Debug helper """ with loglock: if spew_debug: print str def runTransactionInThread(self, func, autostart=True, postcommit=None): """ Runs the desired function in a transaction on a secondary thread. Optionally allows the caller to start the thread manually, and takes an optional postcommit function that is run after the transaction is committed """ def thread_func(transaction_func, postcommit_func): self.first_run.data = True LockingTransaction.runInTransaction(transaction_func) if postcommit_func: postcommit_func() t = Thread(target=thread_func, args=[func, postcommit]) if autostart: t.start() self.spawned_threads.append(t) return t def join_all(self): """ Joins all spawned threads to make sure they have all finished before continuing """ for thread in self.spawned_threads: thread.join() self.spawned_threads = [] def testSimpleConcurrency(self): def t1(): sleep(.1) self.ref0.refSet(1) def t2(): self.ref0.refSet(2) # Delaying t1 means it should commit after t2 self.ref0 = Ref(0, None) self.runTransactionInThread(t1) self.runTransactionInThread(t2) self.join_all() self.assertEqual(self.ref0.deref(), 1) def testFault(self): # We want to cause a fault on one ref, that means no committed value yet t1wait = Event() t1launched = Event() def t1(): # This thread tries to read the value after t2 has written to it, but it starts first self.d("* Before wait") t1wait.wait() val = self.refA.deref() self.d("* Derefed, asserting fault") # Make sure we only successfully got here w/ 1 fault (deref() triggered a retry the first time around) self.assertEqual(self.refA._faults.get(), 1) self.assertEqual(self.refA.historyCount(), 1) self.assertEqual(val, 6) self.d("* Refsetting after fault") # When committed, we should create another tval in the history chain self.refA.refSet(7) def t2(): t1launched.wait() # This thread does the committing after t1 started but before it reads the value of refA self.d("** Creating ref") self.refA = Ref(5, None) self.refA.refSet(6) def t2committed(): self.d("** Notify") t1wait.set() self.runTransactionInThread(t1) self.runTransactionInThread(t2, postcommit=t2committed) self.d("Notifying t1") t1launched.set() self.join_all() # The write after the fault should have created a new history chain item self.assertEqual(self.refA.historyCount(), 2) self.d("Len: %s" % self.refA.historyCount()) def testBarge(self): # Barging happens when one transaction tries to do an in-transaction-write to a ref that has # an in-transaction-value from another transaction t1wait = Event() t2wait = Event() self.t1counter = 0 self.t2counter = 0 def t1(): # We do the first in-transaction-write self.refA.refSet(888) # Don't commit yet, we want t2 to run and barge us self.d("* Notify") t2wait.set() self.d("* Wait") t1wait.wait() self.d("* Done") self.t1counter += 1 def t2(): # Wait till t1 has done its write self.d("** Wait") t2wait.wait() # Try to write to the ref # We should try and succeed to barge them: we were started first # and should be long-lived enough self.d("** Before barge") self.refA.refSet(777) self.d("** After barge") sleep(.1) t1wait.set() self.t2counter += 1 self.refA = Ref(999, None) th1 = self.runTransactionInThread(t1, autostart=False) th2 = self.runTransactionInThread(t2, autostart=False) # Start thread 1 first, give the GIL a cycle so it waits for t2wait th2.start() sleep(.1) th1.start() # Wait for the test to finish self.join_all() # T2 should have successfully barged T1, so T1 should have been re-run # The final value of the ref should be 888, as T1 ran last self.assertEqual(self.t1counter, 2) self.assertEqual(self.t2counter, 1) self.assertEqual(self.refA.deref(), 888) self.d("Final value of ref: %s and t1 ran %s times" % (self.refA.deref(), self.t1counter)) def testCommutes(self): # Make sure multiple transactions that occur simultaneously each commute the same ref # Hard to check for this behaving properly---it should have fewer retries than if each # transaction did an alter, but if transactions commit at the same time one might have to retry # anyway. The difference is usually an order of magnitude, so this test is pretty safe self.numruns = AtomicInteger() self.numalterruns = AtomicInteger() numthreads = 20 def incr(curval): return curval + 1 def adder(curval, extraval): return curval + extraval def t1(): self.numruns.getAndIncrement() self.refA.commute(incr, []) def t2(): # self.d("Thread %s (%s): ALTER BEING RUN, total retry num: %s" % (current_thread().ident, id(current_thread()), self.numalterruns.getAndIncrement())) self.numalterruns.getAndIncrement() self.refB.alter(adder, [100]) self.refA = Ref(0, None) self.refB = Ref(0, None) for i in range(numthreads): self.runTransactionInThread(t1) self.join_all() for i in range(numthreads): self.runTransactionInThread(t2) self.join_all() self.d("Commute took %s runs and counter is %s" % (self.numruns.get(), self.refA.deref())) self.d("Alter took %s runs and counter is %s" % (self.numalterruns.get(), self.refB.deref())) self.assertEqual(self.refA.deref(), numthreads) self.assertEqual(self.refB.deref(), 2000) self.assertTrue(self.numalterruns.get() >= self.numruns.get())
def setUp(self): self.refZero = Ref(0, None)
class TestLockingTransaction(unittest.TestCase): def setUp(self): self.refZero = Ref(0, None) def secondary_op(self, func): """ Utility function, runs the desired function in a secondary thread with its own transaction func should accept two argument: testclass, and the main thread's LockingTransaction """ def thread_func(testclass, mainTransaction, funcToRun): self.assertIsNone(LockingTransaction.get()) LockingTransaction._transactions.local = LockingTransaction() LockingTransaction._transactions.local._info = Info(TransactionState.Running, LockingTransaction._transactions.local._startPoint) funcToRun(testclass, mainTransaction) LockingTransaction._transactions = thread_local() self.assertIsNone(LockingTransaction.get()) t = Thread(target=thread_func, args=[self, LockingTransaction.ensureGet(), func]) t.start() def lockRef(self, ref, reader=False): """ Locks the desired ref's read or write lock. Creates a side thread that never exits, just holds the lock """ def locker(ref, reader): if reader: ref._lock.acquire_shared() else: ref._lock.acquire() t = Thread(target=locker, args=[ref, reader]) t.start() def testNone_PASS(self): self.assertIsNone(LockingTransaction.get()) def testCreateThreadLocal_PASS(self): with running_transaction(self): def secondary(testclass, mainTransaction): testclass.assertIsInstance(LockingTransaction.get(), LockingTransaction) testclass.assertIsInstance(LockingTransaction.ensureGet(), LockingTransaction) # Make sure we're getting a unique locking transaction in this auxiliary thread testclass.assertNotEqual(LockingTransaction.ensureGet(), mainTransaction) self.assertIsInstance(LockingTransaction.get(), LockingTransaction) self.assertIsInstance(LockingTransaction.ensureGet(), LockingTransaction) self.secondary_op(secondary) def testOrdering_PASS(self): with running_transaction(self): t = LockingTransaction.ensureGet() self.assertEqual(t._readPoint, -1) t._updateReadPoint(False) self.assertEqual(t._readPoint, 0) t._updateReadPoint(False) self.assertEqual(t._readPoint, 1) self.assertEqual(t._getCommitPoint(), 2) self.assertEqual(t._readPoint, 1) def testTransactionInfo_PASS(self): with running_transaction(self): # NOTE assumes transactions don't actually work yet (_info is never set) pass def testStop_PASS(self): with running_transaction(self): t = LockingTransaction.ensureGet() # NOTE assumes transactions don't actually work yet (_info is never set) t._stop_transaction(TransactionState.Killed) self.assertIsNone(t._info) # Fake running transaction t._info = Info(TransactionState.Running, t._readPoint) self.assertIsNotNone(t._info) self.assertEqual(t._info.status.get(), TransactionState.Running) t._stop_transaction(TransactionState.Committed) # No way to check for proper status==Committed here since it sets the countdownlatch then immediately sets itself to none self.assertIsNone(t._info) def testTryLock_PASS(self): with running_transaction(self): LockingTransaction.ensureGet()._tryWriteLock(self.refZero) def testBarge_PASS(self): with running_transaction(self): def secondary(testclass, mainTransaction): ourTransaction = LockingTransaction.ensureGet() # Barging should fail as this transaction is too young ourTransaction._info = Info(TransactionState.Running, ourTransaction._readPoint) ourTransaction._startPoint = time() testclass.assertFalse(ourTransaction._barge(mainTransaction._info)) # Barging should still fail, we are the newer transaction sleep(.2) testclass.assertFalse(ourTransaction._barge(mainTransaction._info)) # Fake ourselves being older by resetting our time # Now barging should be successful ourTransaction._startPoint = mainTransaction._startPoint - 2 testclass.assertTrue(ourTransaction._barge(mainTransaction._info)) # Make sure we are still running, and we successfully set the other transaction's # state to Killed testclass.assertEqual(ourTransaction._info.status.get(), TransactionState.Running) testclass.assertEqual(mainTransaction._info.status.get(), TransactionState.Killed) t = LockingTransaction.ensureGet() # For test purposes, force this transaction status to be the desired state t._startPoint = time() t._info = Info(TransactionState.Running, t._startPoint) # Get two transactions on two different threads self.secondary_op(secondary) def testTakeOwnershipBasic_PASS(self): with running_transaction(self): t = LockingTransaction.ensureGet() # Will retry since our readPoint is 0 self.assertRaises(TransactionRetryException, t._takeOwnership, self.refZero) # Make sure we unlocked the lock self.assertRaises(Exception, self.refZero._lock.release) # Now we set the read points synthetically (e.g. saying this transaction-try is starting *now*) # so it appears there is no newer write since # this transaction # Taking ownership should work since no other transactions exist t._readPoint = time() self.assertEqual(t._takeOwnership(self.refZero), 0) self.assertRaises(Exception, self.refZero._lock.release) def testTakeOwnershipLocked_PASS(self): with running_transaction(self): t = LockingTransaction.ensureGet() # Set a write lock on the ref, check we get a retry self.lockRef(self.refZero) self.assertRaises(TransactionRetryException, t._takeOwnership, self.refZero) def testTakeOwnershipBarging_PASS(self): with running_transaction(self): t = LockingTransaction.ensureGet() sleep(.1) LockingTransaction.ensureGet()._updateReadPoint(False) LockingTransaction.ensureGet()._updateReadPoint(False) # Give this ref over to another transaction def secondary(testclass, mainTransaction): t = LockingTransaction.ensureGet() t._startPoint = time() t._info = Info(TransactionState.Running, t._startPoint) # We own the ref now testclass.refZero._tinfo = t._info # give up the ref self.secondary_op(secondary) sleep(.1) # now try to get it back and successfully barge self.assertEqual(t._takeOwnership(self.refZero), 0) self.assertEqual(self.refZero._tinfo, t._info) def testTakeOwnershipBargeFail_PASS(self): with running_transaction(self): t = LockingTransaction.ensureGet() LockingTransaction.ensureGet()._updateReadPoint(False) LockingTransaction.ensureGet()._updateReadPoint(False) def secondary(testclass, mainTransaction): t = LockingTransaction.ensureGet() t._startPoint = time() # We own the ref now testclass.refZero._tinfo = t._info # Try again but time time we won't be successful barging self.secondary_op(secondary) # We fake being newer, so we aren't allowed to barge an older transaction sleep(.2) t._startPoint = time() t._info.startPoint = time() self.assertRaises(TransactionRetryException, t._takeOwnership, self.refZero) self.assertRaises(Exception, self.refZero._lock.release) def testGetRef_PASS(self): # No running transaction self.assertRaises(TransactionRetryException, LockingTransaction().getRef, self.refZero) with running_transaction(self): t = LockingTransaction.ensureGet() # Ref has a previously committed value and no in-transaction-value, so check it # Since we're faking this test, we need our read point to be > 0 LockingTransaction.ensureGet()._updateReadPoint(False) LockingTransaction.ensureGet()._updateReadPoint(False) self.assertEqual(t.getRef(self.refZero), 0) # Make sure we unlocked the ref's lock self.assertRaises(Exception, self.refZero._lock.release_shared) # Give the ref some history and see if it still works. Our read point is 1 and our new value was committed at time 3 self.refZero._tvals = TVal(100, 3, time(), self.refZero._tvals) self.assertEqual(t.getRef(self.refZero), 0) # Now we want the latest value LockingTransaction.ensureGet()._updateReadPoint(False) LockingTransaction.ensureGet()._updateReadPoint(False) LockingTransaction.ensureGet()._updateReadPoint(False) self.assertEqual(t.getRef(self.refZero), 100) # Force the oldest val to be too new to get a fault # Now we have a val at history point 2 and 3 self.refZero._tvals.next.point = 2 t._readPoint = 1 # We should retry and update our faults self.assertRaises(TransactionRetryException, t.getRef, self.refZero) self.assertEqual(self.refZero._faults.get(), 1) def testDoSet_PASS(self): with running_transaction(self): t = LockingTransaction.ensureGet() LockingTransaction.ensureGet()._updateReadPoint(False) LockingTransaction.ensureGet()._updateReadPoint(False) # Do the set and make sure it was set in our various transaction state vars t.doSet(self.refZero, 200) self.assertTrue(self.refZero in t._sets) self.assertTrue(self.refZero in t._vals) self.assertEqual(t._vals[self.refZero], 200) self.assertEqual(self.refZero._tinfo, t._info) def testEnsures_PASS(self): with running_transaction(self): t = LockingTransaction.ensureGet() # Try a normal ensure. Will fail as t has readPoint of -1 and ref has oldest commit point at 0 self.assertRaises(TransactionRetryException, t.doEnsure, self.refZero) self.assertRaises(Exception, self.refZero._lock.release_shared) # Now set our transaction to be further in the future, ensure works LockingTransaction.ensureGet()._updateReadPoint(False) LockingTransaction.ensureGet()._updateReadPoint(False) t.doEnsure(self.refZero) self.assertTrue(self.refZero in t._ensures) # Try again t.doEnsure(self.refZero) # Make sure it's read by releasing the lock exactly once w/out error self.refZero._lock.release_shared() self.assertRaises(Exception, self.refZero._lock.release_shared) def testEnsures_FAIL(self): with running_transaction(self): t = LockingTransaction.ensureGet() LockingTransaction.ensureGet()._updateReadPoint(False) LockingTransaction.ensureGet()._updateReadPoint(False) # Make another transaction to simulate a conflict (failed ensure) # First, write to it in this thread t.doSet(self.refZero, 999) def secondary(testclass, mainTransaction): t = LockingTransaction.ensureGet() LockingTransaction.ensureGet()._updateReadPoint(False) LockingTransaction.ensureGet()._updateReadPoint(False) # Try an ensure that will fail (and cause a bail) testclass.assertRaises(TransactionRetryException, t.doEnsure(testclass.refZero)) self.assertRaises(Exception, self.refZero._lock.release_shared) def testRun_PASS(self): # Now we'll run a transaction ourselves self.refOne = Ref(111, None) self.refTwo = Ref(222, None) # Our transaction body will do a ref-set and an alter (increment a ref by 1) def body(): # Body of the transaction! self.refZero.refSet(999) def incr(val): return val + 1 self.refOne.alter(incr, []) # Test our transaction actually made the changes it should have LockingTransaction.runInTransaction(body) self.assertEqual(self.refZero.deref(), 999) self.assertEqual(self.refOne.deref(), 112) self.assertEqual(self.refTwo.deref(), 222) # Test that the transaction ended properly self.assertRaises(IllegalStateException, self.refZero.refSet, 999)
def setUp(self): self.refZero = Ref(0, None) self.refOne = Ref(pv.vec(range(10)), None)
class TestLockingTransaction(unittest.TestCase): def setUp(self): self.refZero = Ref(0, None) def secondary_op(self, func): """ Utility function, runs the desired function in a secondary thread with its own transaction func should accept two argument: testclass, and the main thread's LockingTransaction """ def thread_func(testclass, mainTransaction, funcToRun): self.assertIsNone(LockingTransaction.get()) LockingTransaction._transactions.local = LockingTransaction() LockingTransaction._transactions.local._info = Info( TransactionState.Running, LockingTransaction._transactions.local._startPoint) funcToRun(testclass, mainTransaction) LockingTransaction._transactions = thread_local() self.assertIsNone(LockingTransaction.get()) t = Thread(target=thread_func, args=[self, LockingTransaction.ensureGet(), func]) t.start() def lockRef(self, ref, reader=False): """ Locks the desired ref's read or write lock. Creates a side thread that never exits, just holds the lock """ def locker(ref, reader): if reader: ref._lock.acquire_shared() else: ref._lock.acquire() t = Thread(target=locker, args=[ref, reader]) t.start() def testNone_PASS(self): self.assertIsNone(LockingTransaction.get()) def testCreateThreadLocal_PASS(self): with running_transaction(self): def secondary(testclass, mainTransaction): testclass.assertIsInstance(LockingTransaction.get(), LockingTransaction) testclass.assertIsInstance(LockingTransaction.ensureGet(), LockingTransaction) # Make sure we're getting a unique locking transaction in this auxiliary thread testclass.assertNotEqual(LockingTransaction.ensureGet(), mainTransaction) self.assertIsInstance(LockingTransaction.get(), LockingTransaction) self.assertIsInstance(LockingTransaction.ensureGet(), LockingTransaction) self.secondary_op(secondary) def testOrdering_PASS(self): with running_transaction(self): t = LockingTransaction.ensureGet() self.assertEqual(t._readPoint, -1) t._updateReadPoint(False) self.assertEqual(t._readPoint, 0) t._updateReadPoint(False) self.assertEqual(t._readPoint, 1) self.assertEqual(t._getCommitPoint(), 2) self.assertEqual(t._readPoint, 1) def testTransactionInfo_PASS(self): with running_transaction(self): # NOTE assumes transactions don't actually work yet (_info is never set) pass def testStop_PASS(self): with running_transaction(self): t = LockingTransaction.ensureGet() # NOTE assumes transactions don't actually work yet (_info is never set) t._stop_transaction(TransactionState.Killed) self.assertIsNone(t._info) # Fake running transaction t._info = Info(TransactionState.Running, t._readPoint) self.assertIsNotNone(t._info) self.assertEqual(t._info.status.get(), TransactionState.Running) t._stop_transaction(TransactionState.Committed) # No way to check for proper status==Committed here since it sets the countdownlatch then immediately sets itself to none self.assertIsNone(t._info) def testTryLock_PASS(self): with running_transaction(self): LockingTransaction.ensureGet()._tryWriteLock(self.refZero) def testBarge_PASS(self): with running_transaction(self): def secondary(testclass, mainTransaction): ourTransaction = LockingTransaction.ensureGet() # Barging should fail as this transaction is too young ourTransaction._info = Info(TransactionState.Running, ourTransaction._readPoint) ourTransaction._startPoint = time() testclass.assertFalse( ourTransaction._barge(mainTransaction._info)) # Barging should still fail, we are the newer transaction sleep(.2) testclass.assertFalse( ourTransaction._barge(mainTransaction._info)) # Fake ourselves being older by resetting our time # Now barging should be successful ourTransaction._startPoint = mainTransaction._startPoint - 2 testclass.assertTrue( ourTransaction._barge(mainTransaction._info)) # Make sure we are still running, and we successfully set the other transaction's # state to Killed testclass.assertEqual(ourTransaction._info.status.get(), TransactionState.Running) testclass.assertEqual(mainTransaction._info.status.get(), TransactionState.Killed) t = LockingTransaction.ensureGet() # For test purposes, force this transaction status to be the desired state t._startPoint = time() t._info = Info(TransactionState.Running, t._startPoint) # Get two transactions on two different threads self.secondary_op(secondary) def testTakeOwnershipBasic_PASS(self): with running_transaction(self): t = LockingTransaction.ensureGet() # Will retry since our readPoint is 0 self.assertRaises(TransactionRetryException, t._takeOwnership, self.refZero) # Make sure we unlocked the lock self.assertRaises(Exception, self.refZero._lock.release) # Now we set the read points synthetically (e.g. saying this transaction-try is starting *now*) # so it appears there is no newer write since # this transaction # Taking ownership should work since no other transactions exist t._readPoint = time() self.assertEqual(t._takeOwnership(self.refZero), 0) self.assertRaises(Exception, self.refZero._lock.release) def testTakeOwnershipLocked_PASS(self): with running_transaction(self): t = LockingTransaction.ensureGet() # Set a write lock on the ref, check we get a retry self.lockRef(self.refZero) self.assertRaises(TransactionRetryException, t._takeOwnership, self.refZero) def testTakeOwnershipBarging_PASS(self): with running_transaction(self): t = LockingTransaction.ensureGet() sleep(.1) LockingTransaction.ensureGet()._updateReadPoint(False) LockingTransaction.ensureGet()._updateReadPoint(False) # Give this ref over to another transaction def secondary(testclass, mainTransaction): t = LockingTransaction.ensureGet() t._startPoint = time() t._info = Info(TransactionState.Running, t._startPoint) # We own the ref now testclass.refZero._tinfo = t._info # give up the ref self.secondary_op(secondary) sleep(.1) # now try to get it back and successfully barge self.assertEqual(t._takeOwnership(self.refZero), 0) self.assertEqual(self.refZero._tinfo, t._info) def testTakeOwnershipBargeFail_PASS(self): with running_transaction(self): t = LockingTransaction.ensureGet() LockingTransaction.ensureGet()._updateReadPoint(False) LockingTransaction.ensureGet()._updateReadPoint(False) def secondary(testclass, mainTransaction): t = LockingTransaction.ensureGet() t._startPoint = time() # We own the ref now testclass.refZero._tinfo = t._info # Try again but time time we won't be successful barging self.secondary_op(secondary) # We fake being newer, so we aren't allowed to barge an older transaction sleep(.2) t._startPoint = time() t._info.startPoint = time() self.assertRaises(TransactionRetryException, t._takeOwnership, self.refZero) self.assertRaises(Exception, self.refZero._lock.release) def testGetRef_PASS(self): # No running transaction self.assertRaises(TransactionRetryException, LockingTransaction().getRef, self.refZero) with running_transaction(self): t = LockingTransaction.ensureGet() # Ref has a previously committed value and no in-transaction-value, so check it # Since we're faking this test, we need our read point to be > 0 LockingTransaction.ensureGet()._updateReadPoint(False) LockingTransaction.ensureGet()._updateReadPoint(False) self.assertEqual(t.getRef(self.refZero), 0) # Make sure we unlocked the ref's lock self.assertRaises(Exception, self.refZero._lock.release_shared) # Give the ref some history and see if it still works. Our read point is 1 and our new value was committed at time 3 self.refZero._tvals = TVal(100, 3, time(), self.refZero._tvals) self.assertEqual(t.getRef(self.refZero), 0) # Now we want the latest value LockingTransaction.ensureGet()._updateReadPoint(False) LockingTransaction.ensureGet()._updateReadPoint(False) LockingTransaction.ensureGet()._updateReadPoint(False) self.assertEqual(t.getRef(self.refZero), 100) # Force the oldest val to be too new to get a fault # Now we have a val at history point 2 and 3 self.refZero._tvals.next.point = 2 t._readPoint = 1 # We should retry and update our faults self.assertRaises(TransactionRetryException, t.getRef, self.refZero) self.assertEqual(self.refZero._faults.get(), 1) def testDoSet_PASS(self): with running_transaction(self): t = LockingTransaction.ensureGet() LockingTransaction.ensureGet()._updateReadPoint(False) LockingTransaction.ensureGet()._updateReadPoint(False) # Do the set and make sure it was set in our various transaction state vars t.doSet(self.refZero, 200) self.assertTrue(self.refZero in t._sets) self.assertTrue(self.refZero in t._vals) self.assertEqual(t._vals[self.refZero], 200) self.assertEqual(self.refZero._tinfo, t._info) def testEnsures_PASS(self): with running_transaction(self): t = LockingTransaction.ensureGet() # Try a normal ensure. Will fail as t has readPoint of -1 and ref has oldest commit point at 0 self.assertRaises(TransactionRetryException, t.doEnsure, self.refZero) self.assertRaises(Exception, self.refZero._lock.release_shared) # Now set our transaction to be further in the future, ensure works LockingTransaction.ensureGet()._updateReadPoint(False) LockingTransaction.ensureGet()._updateReadPoint(False) t.doEnsure(self.refZero) self.assertTrue(self.refZero in t._ensures) # Try again t.doEnsure(self.refZero) # Make sure it's read by releasing the lock exactly once w/out error self.refZero._lock.release_shared() self.assertRaises(Exception, self.refZero._lock.release_shared) def testEnsures_FAIL(self): with running_transaction(self): t = LockingTransaction.ensureGet() LockingTransaction.ensureGet()._updateReadPoint(False) LockingTransaction.ensureGet()._updateReadPoint(False) # Make another transaction to simulate a conflict (failed ensure) # First, write to it in this thread t.doSet(self.refZero, 999) def secondary(testclass, mainTransaction): t = LockingTransaction.ensureGet() LockingTransaction.ensureGet()._updateReadPoint(False) LockingTransaction.ensureGet()._updateReadPoint(False) # Try an ensure that will fail (and cause a bail) testclass.assertRaises(TransactionRetryException, t.doEnsure(testclass.refZero)) self.assertRaises(Exception, self.refZero._lock.release_shared) def testRun_PASS(self): # Now we'll run a transaction ourselves self.refOne = Ref(111, None) self.refTwo = Ref(222, None) # Our transaction body will do a ref-set and an alter (increment a ref by 1) def body(): # Body of the transaction! self.refZero.refSet(999) def incr(val): return val + 1 self.refOne.alter(incr, []) # Test our transaction actually made the changes it should have LockingTransaction.runInTransaction(body) self.assertEqual(self.refZero.deref(), 999) self.assertEqual(self.refOne.deref(), 112) self.assertEqual(self.refTwo.deref(), 222) # Test that the transaction ended properly self.assertRaises(IllegalStateException, self.refZero.refSet, 999)