/// <remarks> /// Not thread safe; ensure this is invoked only by the timer within this class. /// </remarks> private void MonitorWaits(object sender, object e) { foreach (var record in Waits) { // a lock should always be available or added prior to a wait; if not we'll take the null ref exception // that would follow. it should be impossible to hit this so a catastrophic failure is appropriate. Locks.TryGetValue(record.Key, out var recordLock); // enter a read lock first; TryPeek and TryDequeue are atomic so there's no risky operation until later. recordLock.EnterUpgradeableReadLock(); try { if (record.Value.TryPeek(out var nextPendingWait)) { if (nextPendingWait.CancellationToken != null && ((CancellationToken)nextPendingWait.CancellationToken).IsCancellationRequested) { if (record.Value.TryDequeue(out var cancelledWait)) { cancelledWait.TaskCompletionSource.SetException(new OperationCanceledException("The wait was cancelled.")); } } else if (nextPendingWait.DateTime.AddSeconds(nextPendingWait.TimeoutAfter) < DateTime.UtcNow && record.Value.TryDequeue(out var timedOutWait)) { timedOutWait.TaskCompletionSource.SetException(new TimeoutException($"The wait timed out after {timedOutWait.TimeoutAfter} seconds.")); } } if (record.Value.IsEmpty) { // enter the write lock to prevent Wait() (which obtains a read lock) from enqueing any more waits // before we can delete the dictionary record recordLock.EnterWriteLock(); try { // check the queue again to ensure Wait() didn't enqueue anything between the last check and when we // entered the write lock. this is guarateed to be safe since we now have exclusive access to the record if (record.Value.IsEmpty) { Waits.TryRemove(record.Key, out _); Locks.TryRemove(record.Key, out _); } } finally { recordLock.ExitWriteLock(); } } } finally { recordLock.ExitUpgradeableReadLock(); } } }
private void Disposition(WaitKey key, Action <PendingWait> action) { if (Waits.TryGetValue(key, out var queue) && queue.TryDequeue(out var wait)) { action(wait); wait.Dispose(); if (Locks.TryGetValue(key, out var recordLock)) { // enter a read lock first; TryPeek and TryDequeue are atomic so there's no risky operation until later. recordLock.EnterUpgradeableReadLock(); try { // clean up entries in the Waits and Locks dictionaries if the corresponding ConcurrentQueue is empty. // this is tricky, because we don't want to remove a record if another thread is in the process of // enqueueing a new wait. if (queue.IsEmpty) { // enter the write lock to prevent Wait() (which obtains a read lock) from enqueing any more waits // before we can delete the dictionary record. it's ok and expected that Wait() might add this record // back to the dictionary as soon as this unblocks; we're preventing new waits from being discarded if // they are added by another thread just prior to the TryRemove() operation below. recordLock.EnterWriteLock(); try { // check the queue again to ensure Wait() didn't enqueue anything between the last check and when // we entered the write lock. this is guarateed to be safe since we now have exclusive access to // the record and it should be impossible to remove a record containing a non-empty queue if (queue.IsEmpty) { Waits.TryRemove(key, out _); Locks.TryRemove(key, out _); } } finally { recordLock.ExitWriteLock(); } } } finally { recordLock.ExitUpgradeableReadLock(); } } } }
public static void SaveLog(string testName, string log) { if (!Locks.TryGetValue(testName, out var lockObject)) { lockObject = new object(); if (!Locks.TryAdd(testName, lockObject)) { lockObject = Locks[testName]; } } lock (lockObject) { var filePath = System.IO.Path.Combine(Path, testName + ".txt"); Directory.CreateDirectory(Path); File.AppendAllLines(filePath, new[] { log }); } }
public void BeginGuardedOperation() { lock (sync) { if (lockID == Guid.Empty) { throw new InvalidOperationException("Guarded operation " + "was blocked because no lock has been obtained."); } object currentLock; Locks.TryGetValue(lockID, out currentLock); if (currentLock != SlotMarker) { throw new InvalidOperationException("Guarded operation " + "was blocked because the lock was obtained on a " + "different thread from the calling thread."); } } }
private void ReleaseLock() { lock (sync) { if (lockID == Guid.Empty) { throw new InvalidOperationException("This instance cannot " + "be unlocked because no lock currently exists."); } object currentLock; Locks.TryGetValue(lockID, out currentLock); if (currentLock == SlotMarker) { Locks.Remove(lockID); lockID = Guid.Empty; } else { throw new InvalidOperationException("Unlock must be invoked " + "from same thread that invoked Lock."); } } }