Skip to content

Akka.Actor: ReceiveAsync loses track of ActorContext following an await on WaitAsync, Sender throws #6279

@Aaronontheweb

Description

@Aaronontheweb

Version Information
Version of Akka.NET? 1.4.46, 1.5.0-alpha3
Which Akka.NET Modules? Akka.Actor

Describe the bug

When using the following ReceiveAsync statement, the Sender.Tell crashes with an System.NotSupportedException:

ReceiveAsync<SubscribeToEnvironment>(async cmd =>
{
    // NOTE: can't throw here - one subscription failing shouldn't blow up any of the others

    // create a ChannelReader, from which we'll create an IAsyncEnumerable in the service
    var (reader, sink) = ChannelSink.AsReader<IEnvironmentEvent>(100, true, BoundedChannelFullMode.DropOldest)
        .PreMaterialize(Context.Materializer());
    var (sinkRefTask, killSwitch) = StreamRefs.SinkRef<IEnvironmentEvent>()
        .ViaMaterialized(KillSwitches.Single<IEnvironmentEvent>(), Keep.Both)
        .Via(cmd.CancellationToken.AsFlow<IEnvironmentEvent>())
        .To(sink)
        .Run(Context.Materializer());

    try
    {
        using var cts = new CancellationTokenSource(_subscriptionTimeout);
        var sinkRef = await sinkRefTask.WaitAsync(cts.Token).ConfigureAwait(false);

        var resp = await _clusterStateActor.ExecuteCommand(
            new SubscriptionCommands.SubscribeToClusterEvents(_subscriberId, cmd.EnvironmentId, sinkRef),
            cts.Token).ConfigureAwait(false);
        if (resp.Status == ResponseStatus.Success)
        {
            Sender.Tell(new SubscribeSuccess(cmd.EnvironmentId, reader));
        }
        else // no-ops are failures here - no guarantee that the new stream is going to run
        {
            // deliver SubscribeFailed to the client
            Sender.Tell(new SubscribeFailed(cmd.EnvironmentId, resp.Message));
            // kill the stream
            killSwitch.Shutdown();
        }
    }
    catch (Exception ex)
    {
        _log.Error(ex, "Failed to create subscription for [EnvironmentId: {0}]", cmd.EnvironmentId);
        Sender.Tell(new SubscribeFailed(cmd.EnvironmentId, ex.Message));
        killSwitch.Abort(ex);
    }
});

The exception thrown:

System.NotSupportedException: There is no active ActorContext, this is most likely due to use of async operations from within this actor.
   at Akka.Actor.ActorBase.get_Context()
   at Akka.Actor.ActorBase.get_Sender()
   at OpsCenter.ClusterState.Subscriptions.SubscriptionManager.<.ctor>b__9_0(SubscribeToEnvironment cmd) in D:\Repositories\Petabridge\OpsCenter\opscenter-blazor-prototype\src\OpsCenter.ClusterState\Subscriptions\SubscriptionManager.cs:line 94

To Reproduce

Working on this - not sure which part is causing this. await-ing inside ReceiveAsync is pretty common and shouldn't cause issues. I'm wondering if it's WaitAsync combined with ConfigureAwait(false) that is causing the antecedent Task to execute outside of the actor's built-in scheduler.

Expected behavior

Actor should be executing with a valid IActorContext and should be able to access the Sender, Self,, Parent, and Children properties without throwing.

Actual behavior

Actor crashes upon accessing the Sender property.

Additional context

We dealt with lots of issues like this one back in the 2015 era of Akka.NET - so this it's something with the way this code works that is causing it to throw.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions