public ActorConcurrencyLock(ActorBase owner, ActorConcurrencySettings actorConcurrencySettings) { this.owner = owner; this.reentrancyMode = actorConcurrencySettings.ReentrancyMode; this.turnLock = new SemaphoreSlim(1, 1); this.reentrantLock = new SemaphoreSlim(1, 1); this.initialCallContext = Guid.NewGuid().ToString(); this.currentCallContext = this.initialCallContext; this.currentCallCount = 0; this.turnLockTimeout = actorConcurrencySettings.LockTimeout; this.turnLockTimeoutRandomizer = GetRandomizer(this.turnLockTimeout, out turnLockWaitMaxRandomIntervalMillis); }
internal ActorConcurrencySettings(ActorConcurrencySettings other) { this.reentrancyMode = other.ReentrancyMode; this.lockTimeout = other.lockTimeout; }
/// <summary> /// Initializes a new instance of the ActorConcurrencySettings class. /// /// By default the <see cref="ReentrancyMode"/> is <see cref="ActorReentrancyMode.LogicalCallContext"/> with a <see cref="LockTimeout"/> of 60 seconds /// </summary> public ActorConcurrencySettings() { this.reentrancyMode = ActorReentrancyMode.LogicalCallContext; this.lockTimeout = TimeSpan.FromSeconds(60); }
public async Task Acquire( string incomingCallContext, ActorDirtyStateHandler handler, ActorReentrancyMode actorReentrancyMode, CancellationToken cancellationToken) { // acquire the reentrancy lock await this.reentrantLock.WaitAsync(cancellationToken); try { // A new logical call context is appended to every outgoing method call. // The received callContext is of form callContext1callContext2callContext3... // thus to check if incoming call was made from the current calls in progress // we need to check if the incoming call context starts with the currentCallContext var startsWith = incomingCallContext.StartsWith(this.currentCallContext); if (startsWith) { // the incoming call is part of the current call chain // the messaging layer may deliver duplicate messages, therefore if the // incomingCallContext is same as currentCallContext it is a duplicate message // this is because every outgoing call from actors has a new callContext appended // to the currentCallContext if (incomingCallContext.Length == this.currentCallContext.Length) { throw new DuplicateMessageException(string.Format(CultureInfo.CurrentCulture, SR.ErrorDuplicateMessage, this.GetType())); } // // this is a reentrant call // // if the reentrancy is disallowed, throw and exception if (actorReentrancyMode == ActorReentrancyMode.Disallowed) { throw new ReentrancyModeDisallowedException(String.Format(SR.ReentrancyModeDisallowed, this.GetType())); } // if the actor is dirty, do not allow reentrant call to go through // since its not expected that actor state be dirty in a reentrant call // throw exception that flows back to caller. if (this.owner.IsDirty) { throw new ReentrantActorInvalidStateException( string.Format( CultureInfo.CurrentCulture, SR.ReentrantActorDirtyState, this.owner.Id)); } // the currentCallCount must not be zero here as the startsWith comparison can only // be true if the incoming call is part of the current call chain // // we only allow one cycle in the reentrant call chain, so if this is a second reentrant call // then reject it. this also ensures that if multiple calls are made from the actor in parallel // and they reenter the actor only one is allowed // if (this.currentCallCount == 1) { this.currentCallCount++; return; } else { throw new InvalidReentrantCallException(SR.InvalidReentrantCall); } } } finally { this.reentrantLock.Release(); } // // this is not a reentrant call, which means that the caller needs to wait // for its turn to execute this call // var timeout = this.GetTurnLockWaitTimeout(); if (!await this.turnLock.WaitAsync(timeout, cancellationToken)) { throw new ActorConcurrencyLockTimeoutException( string.Format( CultureInfo.CurrentCulture, SR.ConcurrencyLockTimedOut, this.owner.Id, timeout)); } // the caller has the turn lock try { // check if the owner is dirty if (this.owner.IsDirty && handler != null) { // call dirty state handler to handle it await handler(this.owner); } // get the reentrancy lock and initialize it with the current call information // so that if this call were to generate reentrant calls they are allowed await this.reentrantLock.WaitAsync(cancellationToken); try { this.currentCallContext = incomingCallContext; this.currentCallCount = 1; } finally { this.reentrantLock.Release(); } } catch { // dirty handler threw and exception, release the turn lock // and throw the exception back this.turnLock.Release(); throw; } // release the reentrancy lock but continue to hold the turn lock and release // it through ReleaseContext method after this call invocation on the actor is completed // indicate that the turn based concurrency lock is acquired and proceed to // call the method on the actor }
private ActorConcurrencyLock CreateAndInitializeReentrancyGuard(ActorBase owner, ActorReentrancyMode mode) { var settings = new ActorConcurrencySettings() { ReentrancyMode = mode }; var guard = new ActorConcurrencyLock(owner, settings); return(guard); }