/// <summary>
        /// Removes the lock entry if there are no waiters, else just releases the lock.
        /// </summary>
        /// <param name="lockContext"></param>
        /// <param name="transactionId"></param>
        private void RemoveLock(StateManagerLockContext lockContext, long transactionId)
        {
            if (lockContext.GrantorCount == 1)
            {
                // Remove transaction id since this lock is getting disposed withoutr a release.
                if (transactionId != StateManagerConstants.InvalidTransactionId)
                {
                    HashSet <Uri> keyLockCollection = null;
                    this.InflightTransactions.TryRemove(transactionId, out keyLockCollection);
                }

                // Remove the lock, and then dispose it.
                // Once the lock is removed from keylocks, a new lock with the same key can be created but it does not affect correctness.
                StateManagerLockContext valueTobeRemoved = null;
                var removeKey = this.keylocks.TryRemove(lockContext.Key, out valueTobeRemoved);
                Utility.Assert(
                    removeKey,
                    "{0}: RemoveLock: Failed to remove lock for key {1}",
                    this.traceType,
                    lockContext.Key.OriginalString);

                lockContext.Dispose();
                FabricEvents.Events.StateProviderMetadataManagerRemoveLock(
                    this.TraceType,
                    lockContext.Key.OriginalString,
                    transactionId);
            }
            else
            {
                lockContext.Release(transactionId);
            }
        }
        /// <summary>
        /// Gets lock context for the given key.
        /// </summary>
        /// <devnote>Exposed for testability only.</devnote>
        internal StateManagerLockContext GetLockContext(Uri key)
        {
            StateManagerLockContext lockContext = null;

            this.keylocks.TryGetValue(key, out lockContext);
            return(lockContext);
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="StateManagerTransactionContext"/> class.
        /// </summary>
        internal StateManagerTransactionContext(
            long transactionId,
            StateManagerLockContext stateManagerLockContext,
            OperationType operationType)
        {
            Utility.Assert(stateManagerLockContext != null, "stateManagerLockContext cannot be empty.");

            this.LockContext   = stateManagerLockContext;
            this.TransactionId = transactionId;
            this.OperationType = operationType;
        }
        /// <summary>
        /// Acquires lock and adds key to the dictionary.
        /// </summary>
        public async Task AcquireLockAndAddAsync(
            Uri key,
            Metadata metadata,
            TimeSpan timeout,
            CancellationToken cancellationToken)
        {
            StateManagerLockContext stateManagerLockContext = null;

            try
            {
                if (!this.keylocks.ContainsKey(key))
                {
                    var addResult = this.keylocks.TryAdd(
                        key,
                        new StateManagerLockContext(key, metadata.StateProviderId, this));
                    Utility.Assert(
                        addResult,
                        "{0}: AcquireLockAndAddAsync: key {1} add failed because the lock key is already present in the dictionary.",
                        this.traceType,
                        key.OriginalString);
                }

                stateManagerLockContext = this.keylocks[key];
                await stateManagerLockContext.AcquireWriteLockAsync(timeout, cancellationToken).ConfigureAwait(false);

                stateManagerLockContext.GrantorCount++;
                var result = this.inMemoryState.TryAdd(key, metadata);
                Utility.Assert(
                    result,
                    "{0}: AcquireLockAndAddAsync: add has failed because the key {1} is already present.",
                    this.traceType,
                    key.OriginalString);

                // Add to id-state provider map as well.
                result = this.stateProviderIdMap.TryAdd(metadata.StateProviderId, metadata);
                Utility.Assert(
                    result,
                    "{0}: AcquireLockAndAddAsync: failed to add into stateprovideridmap because the key {1} is already present.",
                    this.traceType,
                    key.OriginalString);
            }
            finally
            {
                if (stateManagerLockContext != null)
                {
                    stateManagerLockContext.Release(StateManagerConstants.InvalidTransactionId);
                }
            }
        }
        /// <summary>
        /// Locks for write.
        /// </summary>
        public async Task <StateManagerLockContext> LockForWriteAsync(
            Uri key,
            long stateProviderId,
            TransactionBase transaction,
            TimeSpan timeout,
            CancellationToken cancellationToken)
        {
            Utility.Assert(
                key != null,
                "{0}: LockForWriteAsync: key != null. SPID: {1}",
                this.traceType,
                stateProviderId);
            Utility.Assert(
                stateProviderId != 0,
                "{0}: LockForWriteAsync: stateProviderId is zero. Key: {1} SPID: {2}",
                this.traceType,
                key,
                stateProviderId);
            Utility.Assert(
                transaction != null,
                "{0}: LockForWriteAsync: transaction is null. Key: {1} SPID: {2}",
                this.traceType,
                key,
                stateProviderId);

            FabricEvents.Events.StateProviderMetadataManagerLockForWrite(
                this.TraceType,
                key.OriginalString,
                stateProviderId,
                transaction.Id);

            StateManagerLockContext lockContext = null;

            var incrementIndex = 0;

            while (true)
            {
                lockContext = this.keylocks.GetOrAdd(key, k => new StateManagerLockContext(k, stateProviderId, this));

                // If this transaction already has a write lock on the key lock, then do not acquire the lock, instead just increment the grantor count
                if (lockContext.LockMode == StateManagerLockMode.Write &&
                    this.DoesTransactionContainKeyLock(transaction.Id, key))
                {
                    // It is safe to do this grantor count increment outside the lock since this transaction already holds this lock.
                    lockContext.GrantorCount++;
                    break;
                }
                else if (lockContext.LockMode == StateManagerLockMode.Read &&
                         this.DoesTransactionContainKeyLock(transaction.Id, key))
                {
                    // If this transaction has a read lock on the key lock, then release read lock and acquire the write lock.
                    // SM only supports read committed so it is okay to release this lock.
                    lockContext.Release(transaction.Id);

                    // Increment grantor count by 2 to account for both the operations, but after lock acquisition.
                    incrementIndex = 2;
                }
                else
                {
                    incrementIndex = 1;
                }

                // With the removal of the global lock, when a lock is removed during an inflight transaction this exception can happen.
                // It can be solved by either ref counting or handling disposed exception, since this is a rare situation and ref counting
                // needs to be done every time. Gopalk has acked this recommendation.
                try
                {
                    await lockContext.AcquireWriteLockAsync(timeout, cancellationToken).ConfigureAwait(false);

                    lockContext.GrantorCount = lockContext.GrantorCount + incrementIndex;
                    break;
                }
                catch (ObjectDisposedException)
                {
                    // Ignore and continue
                }
                catch (Exception e)
                {
                    FabricEvents.Events.MetadataManagerAcquireWriteLockException(
                        this.TraceType,
                        key.OriginalString,
                        transaction.Id,
                        e.Message,
                        e.StackTrace);
                    throw;
                }
            }

            HashSet <Uri> keyLocks = this.InflightTransactions.GetOrAdd(transaction.Id, k => new HashSet <Uri>(AbsoluteUriEqualityComparer.Comparer));

            keyLocks.Add(key);

            return(lockContext);
        }
        /// <summary>
        /// Locks for read.
        /// </summary>
        public async Task <StateManagerLockContext> LockForReadAsync(
            Uri key,
            long stateProviderId,
            TransactionBase transaction,
            TimeSpan timeout,
            CancellationToken cancellationToken)
        {
            Utility.Assert(
                key != null,
                "{0}: LockForReadAsync: key != null. SPID: {1}",
                this.traceType,
                stateProviderId);
            Utility.Assert(
                stateProviderId != 0,
                "{0}: LockForReadAsync: stateProviderId is zero. Key: {1} SPID: {2}",
                this.traceType,
                key,
                stateProviderId);
            Utility.Assert(
                transaction != null,
                "{0}: LockForReadAsync: transaction is null. Key: {1} SPID: {2}",
                this.traceType,
                key,
                stateProviderId);

            StateManagerLockContext lockContext = null;

            FabricEvents.Events.StateProviderMetadataManagerLockForRead(
                this.TraceType,
                key.OriginalString,
                stateProviderId,
                transaction.Id);

            while (true)
            {
                lockContext = this.keylocks.GetOrAdd(key, k => new StateManagerLockContext(k, stateProviderId, this));

                // If this transaction already has a write lock on the key lock, then do not acquire the lock, instead increment the grantor count.
                // This check is needed only if the transaction is holding this key in the write mode, in the read mode it can acquire the read lock again
                // as it will not cause a deadlock.
                if (lockContext.LockMode == StateManagerLockMode.Write &&
                    this.DoesTransactionContainKeyLock(transaction.Id, key))
                {
                    // It is safe to do this grantor count increment outside the lock since this transaction already holds a write lock.
                    // This lock is just treated as a write lock.
                    lockContext.GrantorCount++;
                    break;
                }

                // With the removal of the global lock, when a lock is removed during an inflight transaction this exception can happen.
                // It can be solved by either ref counting or handling disposed exception, since this is a rare situation and ref counting
                // needs to be done every time. Gopalk has acked this recommendation.
                try
                {
                    await lockContext.AcquireReadLockAsync(timeout, cancellationToken).ConfigureAwait(false);

                    break;
                }
                catch (ObjectDisposedException)
                {
                    // Ignore and continue
                }
                catch (Exception e)
                {
                    FabricEvents.Events.MetadataManagerAcquireReadLockException(
                        this.TraceType,
                        key.OriginalString,
                        transaction.Id,
                        e.Message,
                        e.StackTrace);
                    throw;
                }
            }

            HashSet <Uri> keyLocks = this.InflightTransactions.GetOrAdd(transaction.Id, k => new HashSet <Uri>(AbsoluteUriEqualityComparer.Comparer));

            keyLocks.Add(key);

            return(lockContext);
        }