akkadotnet / akka.analyzers

Roslyn analyzers for Akka.NET developers.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

AK3000: Custom Akka.Streams `OutGraphStageLogi`s must call `SetHandler` for OUT port

Aaronontheweb opened this issue · comments

Is your feature request related to a problem? Please describe.

So this is a tad tricky because it depends very much on the type of Stage<T> base class used, but for this SourceShape<T> for instance:

// -----------------------------------------------------------------------
// <copyright file="PacketIdSource.cs" company="Petabridge, LLC">
//      Copyright (C) 2024 - 2024 Petabridge, LLC <https://petabridge.com>
// </copyright>
// -----------------------------------------------------------------------

using Akka;
using Akka.Streams;
using Akka.Streams.Stage;

namespace TurboMqtt.Core.Streams;

// create a custom Source<ZonZeroUInt32, NotUsed> that generates unique packet IDs
// for each message that needs to be sent out over the wire
internal sealed class PacketIdSource : GraphStage<SourceShape<ushort>>
{
    public PacketIdSource()
    {
        Shape = new SourceShape<ushort>(Out);
    }

    public Outlet<ushort> Out { get; } = new("PacketIdSource.Out");

    public override SourceShape<ushort> Shape { get; }

    private sealed class Logic(PacketIdSource source) : OutGraphStageLogic(source.Shape) 
    {
        private ushort _currentId = 0;

        public override void OnPull()
        {
            // ensure that we wrap around
            if (_currentId == ushort.MaxValue)
            {
                _currentId = 0;
            }

            // guarantees that we never hit zero
            Push(source.Out, ++_currentId);
        }
    }

    protected override GraphStageLogic CreateLogic(Attributes inheritedAttributes)
    {
        return new Logic(this);
    }
}

The Logic in this case doesn't call SetHandler anywhere, so this stream stage will blow up as soon as it runs for the first time.

Describe the solution you'd like

Depending on the type of Logic used, we should ensure that SetHandler is called somewhere in either the CTOR or one of the other methods invoked inside this actor.

  • For Sinks and Sources, we only need to call SetHandler once
  • For Flow stages we need to ensure it's called twice
  • For Fan-in and Fan-out stages this is more complicated and probably can't be enforced via rule other than ensuring that SetHandler is called AT LEAST ONCE somewhere