internal Bucket(BucketSet owningSet) { _timedOut = false; _index = -1; _size = 1024; // A possible design change here is to have this scale dynamically based on load. _transactions = new InternalTransaction[_size]; _owningSet = owningSet; }
internal TransactionTable() { // Create a timer that is initially disabled by specifing an Infinite time to the first interval _timer = new Timer(new TimerCallback(ThreadTimer), null, Timeout.Infinite, _timerInterval); // Note that the timer is disabled _timerEnabled = false; // Store the timer interval _timerInterval = 1 << TransactionTable.timerInternalExponent; // Ticks start off at zero. _ticks = 0; // The head of the list is long.MaxValue. It contains all of the transactions that for // some reason or other don't have a timeout. _headBucketSet = new BucketSet(this, long.MaxValue); // Allocate the lock _rwLock = new CheapUnfairReaderWriterLock(); }
// Process a timer event private void ThreadTimer(Object state) { // // Theory of operation. // // To timeout transactions we must walk down the list starting from the head // until we find a link with an absolute timeout that is greater than our own. // At that point everything further down in the list is elegable to be timed // out. So simply remove that link in the list and walk down from that point // timing out any transaction that is found. // // There could be a race between this callback being queued and the timer // being disabled. If we get here when the timer is disabled, just return. if (!_timerEnabled) { return; } // Increment the number of ticks _ticks++; _lastTimerTime = DateTime.UtcNow.Ticks; // // First find the starting point of transactions that should time out. Every transaction after // that point will timeout so once we've found it then it is just a matter of traversing the // structure. // BucketSet lastBucketSet = null; BucketSet currentBucketSet = _headBucketSet; // The list always has a head. // Acquire a writer lock before checking to see if we should disable the timer. // Adding of transactions acquires a reader lock and might insert a new BucketSet. // If that races with our check for a BucketSet existing, we may not timeout that // transaction that is being added. WeakReference nextWeakSet = null; BucketSet nextBucketSet = null; nextWeakSet = (WeakReference)currentBucketSet.nextSetWeak; if (nextWeakSet != null) { nextBucketSet = (BucketSet)nextWeakSet.Target; } if (nextBucketSet == null) { _rwLock.EnterWriteLock(); try { // Access the nextBucketSet again in writer lock to account for any race before disabling the timeout. nextWeakSet = (WeakReference)currentBucketSet.nextSetWeak; if (nextWeakSet != null) { nextBucketSet = (BucketSet)nextWeakSet.Target; } if (nextBucketSet == null) { // // Special case to allow for disabling the timer. // // If there are no transactions on the timeout list we can disable the // timer. if (!_timer.Change(Timeout.Infinite, Timeout.Infinite)) { throw TransactionException.CreateInvalidOperationException( TraceSourceType.TraceSourceLtm, SR.UnexpectedTimerFailure, null ); } _timerEnabled = false; return; } } finally { _rwLock.ExitWriteLock(); } } // Note it is slightly subtle that we always skip the head node. This is done // on purpose because the head node contains transactions with essentially // an infinite timeout. do { do { nextWeakSet = (WeakReference)currentBucketSet.nextSetWeak; if (nextWeakSet == null) { // Nothing more to do. return; } nextBucketSet = (BucketSet)nextWeakSet.Target; if (nextBucketSet == null) { // Again nothing more to do. return; } lastBucketSet = currentBucketSet; currentBucketSet = nextBucketSet; }while (currentBucketSet.AbsoluteTimeout > _ticks); // // Pinch off the list at this point making sure it is still the correct set. // // Note: We may lose a race with an "Add" thread that is inserting a BucketSet in this location in // the list. If that happens, this CompareExchange will not be performed and the returned abortingSetsWeak // value will NOT equal nextWeakSet. But we check for that and if this condition occurs, this iteration of // the timer thread will simply return, not timing out any transactions. When the next timer interval // expires, the thread will walk the list again, find the appropriate BucketSet to pinch off, and // then time out the transactions. This means that it is possible for a transaction to live a bit longer, // but not much. WeakReference abortingSetsWeak = (WeakReference)Interlocked.CompareExchange(ref lastBucketSet.nextSetWeak, null, nextWeakSet); if (abortingSetsWeak == nextWeakSet) { // Yea - now proceed to abort the transactions. BucketSet abortingBucketSets = null; do { if (abortingSetsWeak != null) { abortingBucketSets = (BucketSet)abortingSetsWeak.Target; } else { abortingBucketSets = null; } if (abortingBucketSets != null) { abortingBucketSets.TimeoutTransactions(); abortingSetsWeak = (WeakReference)abortingBucketSets.nextSetWeak; } }while (abortingBucketSets != null); // That's all we needed to do. break; } // We missed pulling the right transactions off. Loop back up and try again. currentBucketSet = lastBucketSet; }while (true); }
private void AddIter(InternalTransaction txNew) { // // Theory of operation. // // Note that the head bucket contains any transaction with essentially infinite // timeout (long.MaxValue). The list is sorted in decending order. To add // a node the code must walk down the list looking for a set of bucket that matches // the absolute timeout value for the transaction. When it is found it passes // the insert down to that set. // // An importent thing to note about the list is that forward links are all weak // references and reverse links are all strong references. This allows the GC // to clean up old links in the list so that they don't need to be removed manually. // However if there is still a rooted strong reference to an old link in the // chain that link won't fall off the list because there is a strong reference held // forward. // BucketSet currentBucketSet = _headBucketSet; while (currentBucketSet.AbsoluteTimeout != txNew.AbsoluteTimeout) { BucketSet lastBucketSet = null; do { WeakReference nextSetWeak = (WeakReference)currentBucketSet.nextSetWeak; BucketSet nextBucketSet = null; if (nextSetWeak != null) { nextBucketSet = (BucketSet)nextSetWeak.Target; } if (nextBucketSet == null) { // // We've reached the end of the list either because nextSetWeak was null or // because its reference was collected. This code doesn't care. Make a new // set, attempt to attach it and move on. // BucketSet newBucketSet = new BucketSet(this, txNew.AbsoluteTimeout); WeakReference newSetWeak = new WeakReference(newBucketSet); WeakReference oldNextSetWeak = (WeakReference)Interlocked.CompareExchange( ref currentBucketSet.nextSetWeak, newSetWeak, nextSetWeak); if (oldNextSetWeak == nextSetWeak) { // Ladies and Gentlemen we have a winner. newBucketSet.prevSet = currentBucketSet; } // Note that at this point we don't update currentBucketSet. On the next loop // iteration we should be able to pick up where we left off. } else { lastBucketSet = currentBucketSet; currentBucketSet = nextBucketSet; } }while (currentBucketSet.AbsoluteTimeout > txNew.AbsoluteTimeout); if (currentBucketSet.AbsoluteTimeout != txNew.AbsoluteTimeout) { // // Getting to here means that we've found a slot in the list where this bucket set should go. // BucketSet newBucketSet = new BucketSet(this, txNew.AbsoluteTimeout); WeakReference newSetWeak = new WeakReference(newBucketSet); newBucketSet.nextSetWeak = lastBucketSet.nextSetWeak; WeakReference oldNextSetWeak = (WeakReference)Interlocked.CompareExchange( ref lastBucketSet.nextSetWeak, newSetWeak, newBucketSet.nextSetWeak); if (oldNextSetWeak == newBucketSet.nextSetWeak) { // Ladies and Gentlemen we have a winner. if (oldNextSetWeak != null) { BucketSet oldSet = (BucketSet)oldNextSetWeak.Target; if (oldSet != null) { // prev references are just there to root things for the GC. If this object is // gone we don't really care. oldSet.prevSet = newBucketSet; } } newBucketSet.prevSet = lastBucketSet; } // Special note - We are going to loop back to the BucketSet that preceeds the one we just tried // to insert because we may have lost the race to insert our new BucketSet into the list to another // "Add" thread. By looping back, we check again to see if the BucketSet we just created actually // got added. If it did, we will exit out of the outer loop and add the transaction. But if we // lost the race, we will again try to add a new BucketSet. In the latter case, the BucketSet // we created during the first iteration will simply be Garbage Collected because there are no // strong references to it since we never added the transaction to a bucket and the act of // creating the second BucketSet with remove the backward reference that was created in the // first trip thru the loop. currentBucketSet = lastBucketSet; lastBucketSet = null; // The outer loop will iterate and pick up where we left off. } } // // Great we found a spot. // currentBucketSet.Add(txNew); }