akkadotnet / akka.net

Canonical actor model implementation for .NET with local + distributed actors in C# and F#.
http://getakka.net
Other
4.7k stars 1.04k forks source link

DependencyResolver invokes the wrong constructor #5554

Closed fscavo closed 2 years ago

fscavo commented 2 years ago

Version Information Version of Akka.NET? 1.4.32 Which Akka.NET Modules? Akka.Cluster Akka.Cluster.Sharding

I have an actor with the following constructors defined in this order:

public IdMappingUpsertionWorkflowActor(EventWorkflowOptions options) : base(options) { }
public IdMappingUpsertionWorkflowActor() : this(new EventWorkflowOptions { TerminateOnFailed = false, TerminateOnCompleted = false }) { }

And this is how I create the actor:

var props = DependencyResolver.For(context.System).Props<TWorkflow>(args);
return context.ActorOf(props, $"{typeof(TWorkflow).Name.ToKebabCase()}-{Guid.NewGuid():N}");

DependencyResolver tries to invoke the first constructor (with parameters), though args is empty. If I change the order of constructors definition to be like the following, it works:

public IdMappingUpsertionWorkflowActor() : this(new EventWorkflowOptions { TerminateOnFailed = false, TerminateOnCompleted = false }) { }
public IdMappingUpsertionWorkflowActor(EventWorkflowOptions options) : base(options) { }

This is the exception that throws during actor creation:

[Akka.Actor.OneForOneStrategy] Error while creating actor instance of type Agrifirm.Service.BusinessPartnerAggregation.IdMapping.IdMappingUpsertionWorkflowActor with 0 args: ()
[akka://business-partner-aggregation-service/system/sharding/customer-orchestrator/0/1-01-202201310703037753/id-mapping-upsertion-workflow-actor-b3bb4da4ff154c89a64280fb7ed11943#95192911]: Akka.Actor.ActorInitializationException: Exception during creation
 ---> System.TypeLoadException: Error while creating actor instance of type Agrifirm.Service.BusinessPartnerAggregation.IdMapping.IdMappingUpsertionWorkflowActor with 0 args: ()
 ---> System.InvalidOperationException: Unable to resolve service for type 'Agrifirm.Core.BuildingBlocks.Workflows.EventWorkflowOptions' while attempting to activate 'Agrifirm.Service.BusinessPartnerAggregation.IdMapping.IdMappingUpsertionWorkflowActor'.
   at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.ConstructorMatcher.CreateInstance(IServiceProvider provider)
   at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.CreateInstance(IServiceProvider provider, Type instanceType, Object[] parameters)
   at Akka.DependencyInjection.ServiceProviderActorProducer.Produce()
   at Akka.Actor.Props.NewActor()
   --- End of inner exception stack trace ---
   at Akka.Actor.Props.NewActor()
   at Akka.Actor.ActorCell.CreateNewActorInstance()
   at Akka.Actor.ActorCell.<>c__DisplayClass116_0.<NewActor>b__0()
   at Akka.Actor.ActorCell.UseThreadContext(Action action)
   at Akka.Actor.ActorCell.NewActor()
   at Akka.Actor.ActorCell.Create(Exception failure)
   --- End of inner exception stack trace ---
   at Akka.Actor.ActorCell.Create(Exception failure)
   at Akka.Actor.ActorCell.FinishCreate()
Arkatufus commented 2 years ago

@fscavo First thing I'd have to ask is, if the actor have a zero constructor and you don't have any EventWorkflowOptions services registered in the dependency injection service provider, why are you using DependencyResolver and not a normal Props? I.e. why bother using dependency injection if there are no dependency to inject?

Second this bug is located in dotnet itself. Microsoft.Extensions.DependencyInjection.ActivatorUtilities+ConstructorMatcher.Match(object[] args) returns the very first constructor it inspect when you pass an empty array as args. We could possibly try and fix this internally in our code, but it is a big can of worm because it has a lot of edge cases. @Aaronontheweb what do you think?

Aaronontheweb commented 2 years ago

Marking this as closed - issue is upstream inside Microsoft.Extensions.DependencyInjection

Aaronontheweb commented 2 years ago

I'd like a little more investigation into this issue to see if it's our use of Microsoft.Extensions.DependencyInjection that is at-fault here.

Aaronontheweb commented 2 years ago

Have another data point - user with the following constructor ran into the same failure:

public DistributedPubSubToEventStreamActor(IDistributedPubSub distributedPubSub, string[] subscribeToEvents)

Looks like in this case IDistributedPubSub is passed in via DI but the events are passed in as CTOR args via the usual route inside our DI.

Arkatufus commented 2 years ago

It was a different issue. This specific issue can be tested with this simple console app:

class Program
{
    static void Main(string[] args)
    {
        var provider = new ServiceCollection().BuildServiceProvider();
        ActivatorUtilities.CreateInstance(provider, typeof(GoodClass), Array.Empty<object>());
        Console.WriteLine($"{nameof(GoodClass)} created");

        ActivatorUtilities.CreateInstance(provider, typeof(FailClass), Array.Empty<object>());
        Console.WriteLine($"{nameof(FailClass)} created");
    }
}

public class GoodClass
{
    public GoodClass()
    {}

    public GoodClass(string arg1)
    {}
}

public class FailClass
{
    public FailClass(string arg1)
    {}

    public FailClass()
    {}
}

Output:

PS > dotnet run
GoodClass created
Unhandled exception. System.InvalidOperationException: Unable to resolve service for type 'System.String' while attempting to activate 'Repro.FailClass'.
   at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.ConstructorMatcher.CreateInstance(IServiceProvider provider)
   at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.CreateInstance(IServiceProvider provider, Type instanceType, Object[] parameters)
   at Repro.Program.Main(String[] args) in D:\Repro\Program.cs:line 14
Arkatufus commented 2 years ago

Note the very specific exception message:

System.InvalidOperationException: Unable to resolve service for type 'System.String' while attempting to activate 'Repro.FailClass'.
Aaronontheweb commented 2 years ago

What if the array is populated?