/// <summary> /// Releases all the locks in the reverse order of acquisition. /// </summary> public void Release() { if (this.nodeHaveBeenLocked.Count == 0) { // Allow the lock to be released again... Will enforce the state transition later. return; } else if (NoReleasingAfterLockRelease && this.lockStage == LockStage.Released) { throw new InvalidOperationException($"Cannot call {nameof(this.Release)} in stage Released"); } else if (this.lockStage == LockStage.Locked) { // Immediately mark it as released this.lockStage = LockStage.Released; while (this.nodeHaveBeenLocked.Count > 0) { var lockPair = this.nodeHaveBeenLocked.Pop(); if (lockPair.Value == 0) { lockPair.Key.ReleaseReaderLock(); } else { lockPair.Key.ReleaseWriterLock(); } } RingMasterEventSource.Log.LockCollectionReleased(Thread.CurrentThread.ManagedThreadId); } // Do nothing if locks are collected but not acquired. }
/// <summary> /// Acquires all the locks in a top-down, left-right order. /// </summary> /// <param name="cancelled">Cancellation token to cancel the long-running lock acquisition</param> public void Acquire(ref bool cancelled) { if (this.lockStage != LockStage.AddingLock) { throw new InvalidOperationException($"Cannot call {nameof(this.Acquire)} in stage {this.lockStage}"); } if (this.hasWriterLock) { this.RemoveRedundantLocks(); } // Mark it before potential cancellation this.lockStage = LockStage.Locked; // For O(1) lock collision check. Collision can only happen within a level, not between different levels. // Index refers to the index of the lock object in the following locks list. var lockSet = new Dictionary <ILockObject, int>(); // Ordered collection of each lock to be acquired var locks = new List <KeyValuePair <ILockObject, byte> >(); // For logging and debugging var sb = new StringBuilder(); for (int level = 0; level < this.nodeToBeLocked.Length && !cancelled; level++) { locks.Clear(); var idx = 0; // lock index for debugging foreach (var nodeLock in this.nodeToBeLocked[level]) { var lockRequired = nodeLock.Value; var lockObj = nodeLock.Key.GetLockObject(level, lockRequired == WriterLockValue); // In read free case, no lock is required. if (lockObj == null) { continue; } // Lock collision found. No operation if current one is read because previous one is at least read. int existingLockObjIndex; if (lockSet.TryGetValue(lockObj, out existingLockObjIndex)) { // Ignore the collision if the reader is acquired. if (lockRequired == WriterLockValue && locks[existingLockObjIndex].Value == ReaderLockValue) { locks[existingLockObjIndex] = new KeyValuePair <ILockObject, byte>(lockObj, WriterLockValue); sb.AppendLine(string.Join(",", level.ToString(), nodeLock.Key.Name, nodeLock.Key.BuildPath(), lockRequired.ToString(), "C")); } } else { // No collision lockSet.Add(lockObj, idx); idx++; locks.Add(new KeyValuePair <ILockObject, byte>(lockObj, lockRequired)); sb.AppendLine(string.Join(",", level.ToString(), nodeLock.Key.Name, nodeLock.Key.BuildPath(), lockRequired.ToString())); } } idx = 0; foreach (var lockOp in locks) { if (cancelled) { break; } var startTime = this.clock.Elapsed; var succeeded = lockOp.Value == ReaderLockValue ? lockOp.Key.AcquireReaderLock(MaxAcquireLockTime) : lockOp.Key.AcquireWriterLock(MaxAcquireLockTime); RingMasterServerInstrumentation.Instance.OnAcquireLock(true, succeeded, level, this.clock.Elapsed - startTime); if (!succeeded) { throw new RetriableOperationException( $"Lock acquisition timed out after {MaxAcquireLockTime.TotalMilliseconds}: level {level} index {idx} in {sb}"); } this.nodeHaveBeenLocked.Push(lockOp); idx++; } } RingMasterEventSource.Log.LockCollectionAcquired(Thread.CurrentThread.ManagedThreadId, sb.ToString()); }