private void ForwardOrDelay(ByteString payload)
 {
     if (InboundThrottleMode is Blackhole)
     {
         // Do nothing
     }
     else
     {
         if (!ThrottledMessages.Any())
         {
             var tokens    = payload.Length;
             var res       = InboundThrottleMode.TryConsumeTokens(SystemNanoTime.GetNanos(), tokens);
             var newBucket = res.Item1;
             var success   = res.Item2;
             if (success)
             {
                 InboundThrottleMode = newBucket;
                 UpstreamListener.Notify(new InboundPayload(payload));
             }
             else
             {
                 ThrottledMessages.Enqueue(payload);
                 ScheduleDequeue(InboundThrottleMode.TimeToAvailable(SystemNanoTime.GetNanos(), tokens));
             }
         }
         else
         {
             ThrottledMessages.Enqueue(payload);
         }
     }
 }
        private void InitializeFSM()
        {
            When(ThrottlerState.WaitExposedHandle, @event =>
            {
                if (@event.FsmEvent is ThrottlerManager.Handle && @event.StateData is Uninitialized)
                {
                    // register to downstream layer and wait for origin
                    OriginalHandle.ReadHandlerSource.SetResult(new ActorHandleEventListener(Self));
                    return
                    (GoTo(ThrottlerState.WaitOrigin)
                     .Using(
                         new ExposedHandle(
                             @event.FsmEvent.AsInstanceOf <ThrottlerManager.Handle>().ThrottlerHandle)));
                }
                return(null);
            });

            When(ThrottlerState.WaitOrigin, @event =>
            {
                if (@event.FsmEvent is InboundPayload && @event.StateData is ExposedHandle)
                {
                    var b = @event.FsmEvent.AsInstanceOf <InboundPayload>().Payload;
                    ThrottledMessages.Enqueue(b);
                    var origin = PeekOrigin(b);
                    if (origin != null)
                    {
                        Manager.Tell(new ThrottlerManager.Checkin(origin, @event.StateData.AsInstanceOf <ExposedHandle>().Handle));
                        return(GoTo(ThrottlerState.WaitMode));
                    }
                    return(Stay());
                }
                return(null);
            });

            When(ThrottlerState.WaitMode, @event =>
            {
                if (@event.FsmEvent is InboundPayload)
                {
                    var b = @event.FsmEvent.AsInstanceOf <InboundPayload>().Payload;
                    ThrottledMessages.Enqueue(b);
                    return(Stay());
                }

                if (@event.FsmEvent is ThrottleMode && @event.StateData is ExposedHandle)
                {
                    var mode            = @event.FsmEvent.AsInstanceOf <ThrottleMode>();
                    var exposedHandle   = @event.StateData.AsInstanceOf <ExposedHandle>().Handle;
                    InboundThrottleMode = mode;
                    try
                    {
                        if (mode is Blackhole)
                        {
                            ThrottledMessages = new Queue <ByteString>();
                            exposedHandle.Disassociate();
                            return(Stop());
                        }
                        else
                        {
                            AssociationHandler.Notify(new InboundAssociation(exposedHandle));
                            var self = Self;
                            exposedHandle.ReadHandlerSource.Task.ContinueWith(
                                r => new ThrottlerManager.Listener(r.Result),
                                TaskContinuationOptions.AttachedToParent & TaskContinuationOptions.ExecuteSynchronously)
                            .PipeTo(self);
                            return(GoTo(ThrottlerState.WaitUpstreamListener));
                        }
                    }
                    finally
                    {
                        Sender.Tell(SetThrottleAck.Instance);
                    }
                }

                return(null);
            });

            When(ThrottlerState.WaitUpstreamListener, @event =>
            {
                if (@event.FsmEvent is InboundPayload)
                {
                    var b = @event.FsmEvent.AsInstanceOf <InboundPayload>();
                    ThrottledMessages.Enqueue(b.Payload);
                    return(Stay());
                }

                if (@event.FsmEvent is ThrottlerManager.Listener)
                {
                    UpstreamListener = @event.FsmEvent.AsInstanceOf <ThrottlerManager.Listener>().HandleEventListener;
                    Self.Tell(new Dequeue());
                    return(GoTo(ThrottlerState.Throttling));
                }

                return(null);
            });

            When(ThrottlerState.WaitModeAndUpstreamListener, @event =>
            {
                if (@event.FsmEvent is ThrottlerManager.ListenerAndMode)
                {
                    var listenerAndMode = @event.FsmEvent.AsInstanceOf <ThrottlerManager.ListenerAndMode>();
                    UpstreamListener    = listenerAndMode.HandleEventListener;
                    InboundThrottleMode = listenerAndMode.Mode;
                    Self.Tell(new Dequeue());
                    return(GoTo(ThrottlerState.Throttling));
                }

                if (@event.FsmEvent is InboundPayload)
                {
                    var b = @event.FsmEvent.AsInstanceOf <InboundPayload>();
                    ThrottledMessages.Enqueue(b.Payload);
                    return(Stay());
                }

                return(null);
            });

            When(ThrottlerState.Throttling, @event =>
            {
                if (@event.FsmEvent is ThrottleMode)
                {
                    var mode            = @event.FsmEvent.AsInstanceOf <ThrottleMode>();
                    InboundThrottleMode = mode;
                    if (mode is Blackhole)
                    {
                        ThrottledMessages = new Queue <ByteString>();
                    }
                    CancelTimer(DequeueTimerName);
                    if (ThrottledMessages.Any())
                    {
                        ScheduleDequeue(InboundThrottleMode.TimeToAvailable(SystemNanoTime.GetNanos(), ThrottledMessages.Peek().Length));
                    }
                    Sender.Tell(SetThrottleAck.Instance);
                    return(Stay());
                }

                if (@event.FsmEvent is InboundPayload)
                {
                    ForwardOrDelay(@event.FsmEvent.AsInstanceOf <InboundPayload>().Payload);
                    return(Stay());
                }

                if (@event.FsmEvent is Dequeue)
                {
                    if (ThrottledMessages.Any())
                    {
                        var payload = ThrottledMessages.Dequeue();
                        UpstreamListener.Notify(new InboundPayload(payload));
                        InboundThrottleMode = InboundThrottleMode.TryConsumeTokens(SystemNanoTime.GetNanos(),
                                                                                   payload.Length).Item1;
                        if (ThrottledMessages.Any())
                        {
                            ScheduleDequeue(InboundThrottleMode.TimeToAvailable(SystemNanoTime.GetNanos(), ThrottledMessages.Peek().Length));
                        }
                    }
                    return(Stay());
                }

                return(null);
            });

            WhenUnhandled(@event =>
            {
                // we should always set the throttling mode
                if (@event.FsmEvent is ThrottleMode)
                {
                    InboundThrottleMode = @event.FsmEvent.AsInstanceOf <ThrottleMode>();
                    Sender.Tell(SetThrottleAck.Instance);
                    return(Stay());
                }

                if (@event.FsmEvent is Disassociated)
                {
                    return(Stop()); // not notifying the upstream handler is intentional: we are relying on heartbeating
                }

                if (@event.FsmEvent is FailWith)
                {
                    var reason = @event.FsmEvent.AsInstanceOf <FailWith>().FailReason;
                    if (UpstreamListener != null)
                    {
                        UpstreamListener.Notify(new Disassociated(reason));
                    }
                    return(Stop());
                }

                return(null);
            });

            if (Inbound)
            {
                StartWith(ThrottlerState.WaitExposedHandle, Uninitialized.Instance);
            }
            else
            {
                OriginalHandle.ReadHandlerSource.SetResult(new ActorHandleEventListener(Self));
                StartWith(ThrottlerState.WaitModeAndUpstreamListener, Uninitialized.Instance);
            }
        }