public DefaultProducerStageLogic(IProducerStage <K, V, P, TIn, TOut> stage, Attributes attributes) : base(stage.Shape)
        {
            _stage = stage;

            var supervisionStrategy = attributes.GetAttribute <ActorAttributes.SupervisionStrategy>(null);

            _decider = supervisionStrategy != null ? supervisionStrategy.Decider : Deciders.StoppingDecider;

            _checkForCompletionCallback = GetAsyncCallback(CheckForCompletion);
            _failStageCallback          = GetAsyncCallback <Exception>(FailStage);

            SetHandler(_stage.In,
                       onPush: () =>
            {
                var msg = Grab(_stage.In) as IEnvelope <K, V, P>;

                switch (msg)
                {
                case Message <K, V, P> message:
                    {
                        var result = new TaskCompletionSource <IResults <K, V, P> >();
                        AwaitingConfirmation.IncrementAndGet();
                        Producer.Produce(message.Record, BuildSendCallback(result, onSuccess: report =>
                        {
                            result.SetResult(new Result <K, V, P>(report, message));
                        }));
                        PostSend(msg);
                        Push(stage.Out, result.Task as Task <TOut>);
                        break;
                    }

                case MultiMessage <K, V, P> multiMessage:
                    {
                        var tasks = multiMessage.Records.Select(record =>
                        {
                            var result = new TaskCompletionSource <MultiResultPart <K, V> >();
                            AwaitingConfirmation.IncrementAndGet();
                            Producer.Produce(record, BuildSendCallback(result, report =>
                            {
                                result.SetResult(new MultiResultPart <K, V>(report, record));
                            }));
                            return(result.Task);
                        });
                        PostSend(msg);
                        var resultTask = Task.WhenAll(tasks).ContinueWith(t => new MultiResult <K, V, P>(t.Result.ToImmutableHashSet(), multiMessage.PassThrough) as IResults <K, V, P>);
                        Push(stage.Out, resultTask as Task <TOut>);
                        break;
                    }

                case PassThroughMessage <K, V, P> passThroughMessage:
                    {
                        PostSend(msg);
                        var resultTask = Task.FromResult(new PassThroughResult <K, V, P>(passThroughMessage.PassThrough) as IResults <K, V, P>);
                        Push(stage.Out, resultTask as Task <TOut>);
                        break;
                    }
                }
            },
                       onUpstreamFinish: () =>
            {
                _inIsClosed = true;
                _completionState.SetResult(NotUsed.Instance);
                _checkForCompletionCallback();
            },
                       onUpstreamFailure: exception =>
            {
                _inIsClosed = true;
                _completionState.SetException(exception);
                _checkForCompletionCallback();
            });

            SetHandler(_stage.Out, onPull: () =>
            {
                TryPull(_stage.In);
            });
        }
        public DefaultProducerStageLogic(IProducerStage <K, V, P, TIn, TOut> stage, Attributes attributes) : base(stage.Shape)
        {
            _stage = stage;

            // TODO: Move this to the GraphStage.InitialAttribute when it is fixed (https://github.com/akkadotnet/akka.net/issues/5388)
            var supervisionStrategy = attributes.GetAttribute(new ActorAttributes.SupervisionStrategy(new DefaultProducerDecider <K, V>().Decide));

            _decider = supervisionStrategy.Decider;

            SetHandler(_stage.In,
                       onPush: () =>
            {
                var msg = Grab(_stage.In) as IEnvelope <K, V, P>;

                switch (msg)
                {
                case Message <K, V, P> message:
                    {
                        var result = new TaskCompletionSource <IResults <K, V, P> >();
                        AwaitingConfirmation.IncrementAndGet();
                        try
                        {
                            var callback = BuildSendCallback(
                                completion: result,
                                onSuccess: report =>
                            {
                                result.SetResult(new Result <K, V, P>(report, message));
                            },
                                onFailure: OnProduceFailure);
                            Producer.Produce(message.Record, GetAsyncCallback(callback));
                            PostSend(msg);
                            Push(stage.Out, result.Task as Task <TOut>);
                        }
                        catch (Exception exception)
                        {
                            OnProduceFailure(exception);
                        }
                        break;
                    }

                case MultiMessage <K, V, P> multiMessage:
                    {
                        var tasks = multiMessage.Records.Select(record =>
                        {
                            var result = new TaskCompletionSource <MultiResultPart <K, V> >();
                            AwaitingConfirmation.IncrementAndGet();
                            var callback = BuildSendCallback(
                                completion: result,
                                onSuccess: report =>
                            {
                                result.SetResult(new MultiResultPart <K, V>(report, record));
                            },
                                onFailure: OnProduceFailure);
                            try
                            {
                                Producer.Produce(record, GetAsyncCallback(callback));
                                return(result.Task);
                            }
                            catch (Exception exception)
                            {
                                OnProduceFailure(exception);
                                return(null);
                            }
                        }).Where(t => t != null).ToArray();
                        if (tasks.Length > 0)
                        {
                            PostSend(msg);
                            var resultTask = Task.WhenAll(tasks).ContinueWith(t => new MultiResult <K, V, P>(t.Result.ToImmutableHashSet(), multiMessage.PassThrough) as IResults <K, V, P>);
                            Push(stage.Out, resultTask as Task <TOut>);
                        }
                        else
                        {
                            TryPull(_stage.In);
                        }
                        break;
                    }

                case PassThroughMessage <K, V, P> passThroughMessage:
                    {
                        PostSend(msg);
                        var resultTask = Task.FromResult(new PassThroughResult <K, V, P>(passThroughMessage.PassThrough) as IResults <K, V, P>);
                        Push(stage.Out, resultTask as Task <TOut>);
                        break;
                    }
                }
            },
                       onUpstreamFinish: () =>
            {
                _completionState.SetResult(NotUsed.Instance);
                CheckForCompletion();
            },
                       onUpstreamFailure: exception =>
            {
                _completionState.SetException(exception);
                CheckForCompletion();
            });

            SetHandler(_stage.Out, onPull: () =>
            {
                TryPull(_stage.In);
            });
        }