/// <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); }