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);
 }
示例#2
0
 internal ActorConcurrencySettings(ActorConcurrencySettings other)
 {
     this.reentrancyMode = other.ReentrancyMode;
     this.lockTimeout    = other.lockTimeout;
 }
示例#3
0
 /// <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
        }
示例#5
0
        private ActorConcurrencyLock CreateAndInitializeReentrancyGuard(ActorBase owner, ActorReentrancyMode mode)
        {
            var settings = new ActorConcurrencySettings()
            {
                ReentrancyMode = mode
            };
            var guard = new ActorConcurrencyLock(owner, settings);

            return(guard);
        }