Stonpid
Stonpid

Reputation: 349

Akka.DI.Autofac did not create actor

I am trying to set up DI followed by official akka.net document (http://getakka.net/docs/Dependency%20injection#autofac). However Actors never create. What's wrong in the following my code?

public class Worker: ReceiveActor
{
    public Worker()
    {
        Receive<string>(m => Console.WriteLine("Worker is working"));
    }
}

public class WorkerManager : ReceiveActor
{
    public WorkerManager()
    {
        Receive<string>(m => Console.WriteLine("Manager start supervise"));
    }

    protected override void PreStart()
    {
        Context.ActorOf(Context.DI().Props<Worker>(), "Worker1");
    }
}

class Program
{
    static void Main(string[] args)
    {
        ContainerBuilder builder = new ContainerBuilder();
        builder.RegisterType<Worker>();
        builder.RegisterType<WorkerManager>();

        var system = ActorSystem.Create("DiTestSystem");

        IContainer container = builder.Build();
        IDependencyResolver resolver = new AutoFacDependencyResolver(container, system);
        var manageRef = system.ActorOf(system.DI().Props<WorkerManager>(), "Manager1");

        manageRef.Tell("Hello");
        system.ActorSelection("/user/Manager1/Worker1").Tell("Hello");

        Console.ReadLine();
    }
}

When run the code, I got this

[INFO][24/04/2017 1:50:11 AM][Thread 0006][akka://DiTestSystem/user/Manager1/Worker1] Message String from akka://DiTestSystem/deadLetters to akka://DiTestSystem/user/Manager1/Worker1 was not delivered. 1 dead letters encountered. Manager start supervise

Upvotes: 0

Views: 850

Answers (2)

Alex Meyer-Gleaves
Alex Meyer-Gleaves

Reputation: 3831

The problem here is a race condition. This could happen regardless of whether or not you are creating your actors using a DI container.

When working with an actor system you need to remember that everything happens asynchronously. You code is allowed to continue after sending the message to the WorkerManager actor but that does not mean that message has actually been received and processed. It is actually posted to a mailbox for the actor to process on a different thread when it is ready.

It would also be best to not attempt to access the Worker actor directly given that you have a WorkerManager actor in place. Just like in real life, you would normally ask a manager to organise for some work to be done, and the manager would then in turn decide what workers are required, and assign the necessary work to them.

Akka.NET has a router feature that is useful for such scenarios. I have updated your sample code with some additional logging and the router configuration.

void Main()
{
    ContainerBuilder builder = new ContainerBuilder();
    builder.RegisterType<Worker>();
    builder.RegisterType<WorkerManager>();
    var container = builder.Build();

    var system = ActorSystem.Create("DITestSystem");
    var resolver = new AutoFacDependencyResolver(container, system);

    var manager = system.ActorOf(system.DI().Props<WorkerManager>(), "Manager");
    Console.WriteLine("Program: Created Manager");

    for (int i = 0; i < 10; i++)
    {
        manager.Tell("Hello");
    }

    Console.ReadKey(true);
}

public class Worker : ReceiveActor
{
    public Worker()
    {
        Receive<string>(m => Console.WriteLine($"Worker {Context.Self.Path.Name} received: {m}"));
    }

    protected override void PreStart()
    {
        Console.WriteLine($"PreStart: {Context.Self.Path}");
    }
}

public class WorkerManager : ReceiveActor
{
    IActorRef worker;

    public WorkerManager()
    {
        Receive<string>(m =>
        {
            Console.WriteLine($"Manager received: {m}");
            worker.Tell(m);
        });
    }

    protected override void PreStart()
    {
        Console.WriteLine($"PreStart: {Context.Self.Path}");

        var props = Context.DI().Props<Worker>().WithRouter(new RoundRobinPool(5));
        worker = Context.ActorOf(props, "Worker");
    }
}

Note this line in the WorkerManager actor that configures a router for the Worker actors.

var props = Context.DI().Props<Worker>().WithRouter(new RoundRobinPool(5));

This will cause the ManagerActor to forward messages to 5 child Worker actors in a round-robin manner. The message routing is taken care of for you so there is no need to interact directly with the Worker actors.

The sample now also fires off 10 messages to the ManagerActor immediately after getting a reference to it.

for (int i = 0; i < 10; i++)
{
    manageRef.Tell("Hello");
}

With more messages in play and some logging you can see that the order of things is not deterministic. This is the output from one run.

Program: Created Manager
PreStart: akka://DITestSystem/user/Manager
Manager received: Hello
Manager received: Hello
Manager received: Hello
Manager received: Hello
Manager received: Hello
Manager received: Hello
Manager received: Hello
Manager received: Hello
Manager received: Hello
Manager received: Hello
PreStart: akka://DITestSystem/user/Manager/Worker/$b
PreStart: akka://DITestSystem/user/Manager/Worker/$d
PreStart: akka://DITestSystem/user/Manager/Worker/$f
Worker $b received: Hello
Worker $b received: Hello
Worker $f received: Hello
Worker $f received: Hello
PreStart: akka://DITestSystem/user/Manager/Worker/$c
Worker $c received: Hello
Worker $c received: Hello
PreStart: akka://DITestSystem/user/Manager/Worker/$e
Worker $d received: Hello
Worker $d received: Hello
Worker $e received: Hello
Worker $e received: Hello

And this is the output from another.

Program: Created Manager
PreStart: akka://DITestSystem/user/Manager
PreStart: akka://DITestSystem/user/Manager/Worker/$b
PreStart: akka://DITestSystem/user/Manager/Worker/$c
PreStart: akka://DITestSystem/user/Manager/Worker/$d
PreStart: akka://DITestSystem/user/Manager/Worker/$f
PreStart: akka://DITestSystem/user/Manager/Worker/$e
Manager received: Hello
Manager received: Hello
Manager received: Hello
Manager received: Hello
Worker $d received: Hello
Worker $e received: Hello
Manager received: Hello
Manager received: Hello
Manager received: Hello
Manager received: Hello
Manager received: Hello
Worker $f received: Hello
Worker $e received: Hello
Manager received: Hello
Worker $f received: Hello
Worker $b received: Hello
Worker $b received: Hello
Worker $c received: Hello
Worker $c received: Hello
Worker $d received: Hello

You can see that in the first run the WorkerManager had received all messages before creating any child Worker actors. In the second run it had created all the child Worker actors before receiving any of the messages.

The main point to remember is that it is best to communicate with messages and not make assumptions about when things happen.

Upvotes: 1

Stonpid
Stonpid

Reputation: 349

What I found is Actor cannot be created fast enough with Autofac.

When I added Thread.Sleep(20), finally, create actor properly.

static void Main(string[] args)
{
    ContainerBuilder builder = new ContainerBuilder();
    builder.RegisterType<WorkerManager>();
    builder.RegisterType<Worker>();

    var system = ActorSystem.Create("DiTestSystem");

    IContainer container = builder.Build();
    IDependencyResolver resolver = new AutoFacDependencyResolver(container, system);
    var manageRef = system.ActorOf(system.DI().Props<WorkerManager>(), "Manager1");

    Thread.Sleep(20); // ADDED THIS LINE
    manageRef.Tell("Hello");
    system.ActorSelection("/user/Manager1/Worker1").Tell("Hello");

    Console.ReadLine();
}

I think it is quite big problem, because this approach cannot guarantee actor always create/resolve during sleep time.

I keep open this problem, because I think it cannot be a proper answer.

Upvotes: 1

Related Questions