예제 #1
0
        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)
예제 #2
0
    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))
예제 #3
0
    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)
예제 #4
0
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)
예제 #5
0
    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())
예제 #6
0
    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)
예제 #7
0
    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)
예제 #8
0
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)
예제 #9
0
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())
예제 #10
0
 def setUp(self):
     self.refZero = Ref(0, None)
예제 #11
0
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)
예제 #12
0
 def setUp(self):
     self.refZero = Ref(0, None)
     self.refOne = Ref(pv.vec(range(10)), None)
예제 #13
0
 def setUp(self):
     self.refZero = Ref(0, None)
     self.refOne = Ref(pv.vec(range(10)), None)
예제 #14
0
 def setUp(self):
     self.refZero = Ref(0, None)
예제 #15
0
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)