/// <summary> /// Proper constructor. /// </summary> /// <param name="id">Store transaction id.</param> /// <param name="isReadOnly">True, if the store transaction is read only.</param> /// <param name="owner"></param> /// <param name="replicatorTransaction"></param> /// <param name="tracer"></param> /// <param name="enableStrict2PL">Indicates whether strict 2PL is enabled.</param> public StoreTransaction( long id, bool isReadOnly, IStoreTransactionProvider owner, ReplicatorTransactionBase replicatorTransaction, string tracer, bool enableStrict2PL) { this.id = id; this.isReadOnly = isReadOnly; this.tracer = tracer; this.owner = owner; this.replicatorTransactionBase = replicatorTransaction; this.enableStrict2PL = enableStrict2PL; }
/// <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> /// The apply async. /// </summary> /// <param name="lsn"> /// The lsn. /// </param> /// <param name="transactionBase"> /// The transaction base. /// </param> /// <param name="data"> /// The data. /// </param> /// <param name="applyContext"> /// The apply context. /// </param> /// <returns> /// The <see cref="Task"/>. /// </returns> Task <object> IStateProvider2.ApplyAsync( long lsn, TransactionBase transactionBase, OperationData data, ApplyContext applyContext) { // Get the operation. var operation = Operation.Deserialize(data[0]); // Resume the existing transaction for this operation or start a transaction for this operation. bool applied; OperationContext context; DatabaseTransaction <TKey, TValue> tx; if (IsPrimaryOperation(applyContext) && this.inProgressOperations.TryGetValue(operation.Id, out context)) { applied = true; tx = context.DatabaseTransaction; tx.Resume(); } else { // The operation has not yet been applied and therefore a transaction has not been initiated. applied = false; tx = this.tables.CreateTransaction(); } /*var part = this.replicator.StatefulPartition; * var operationString = JsonConvert.SerializeObject(operation, SerializationSettings.JsonConfig); * Trace.TraceInformation( * $"[{this.partitionId}/{this.replicator.InitializationParameters.ReplicaId} r:{part.ReadStatus} w:{part.WriteStatus}] ApplyAsync(lsn: {lsn}, tx: {transactionBase.Id}, op: {operationString} (length: {data?.Length ?? 0}), context: {applyContext})"); */ try { // If the operation has not yet been applied, apply it. if (!applied) { //Trace.TraceInformation($"{applyContext} Apply {operationString}"); operation.Apply(tx.Table); } //Trace.TraceInformation($"{applyContext} Commit {operationString}"); tx.Commit(); } catch (Exception exception) { tx.Rollback(); return(Task.FromException <object>(exception)); } finally { if (IsPrimaryOperation(applyContext)) { this.inProgressOperations.TryRemove(operation.Id, out context); } tx.Dispose(); } return(Task.FromResult(default(object))); }
/// <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); }