Search Results for

    Show / Hide Table of Contents

    Part 1: Top Level Architecture

    When writing prose, the hardest part is usually to write the first couple of sentences. There is a similar feeling when trying to build an Akka.NET system: What should be the first actor? Where should it live? What should it do? Fortunately, unlike with prose, there are established best practices that can guide us through these initial steps.

    When one creates an actor in Akka.NET it always belongs to a certain parent. This means that actors are always organized into a tree. In general, creating an actor can only happen from inside another actor. This 'creator' actor becomes the parent of the newly created child actor. You might ask then, who is the parent of the first actor you create? To create a top-level actor one must first initialize an actor system, let's refer to this as the object System. This is followed by a call to System.ActorOf() which returns a reference to the newly created actor. This does not create a "freestanding" actor though, instead, it injects the corresponding actor as a child into an already existing tree:

    box diagram of the architecture

    As you see, creating actors from the "top" injects those actors under the path /user/, so for example creating an actor named myActor will end up having the path /user/myActor. In fact, there are three already existing actors in the system:

    • / the so-called root guardian. This is the parent of all actors in the system, and the last one to stop when the system itself is terminated.
    • /user the user guardian. This is the parent actor for all user created actors. The name user should not confuse you, it has nothing to do with the logged in user, nor user handling in general. This name really means userspace as this is the place where actors that do not access Akka.NET internals live, i.e. all the actors created by users of the Akka.NET library. Every actor you will create will have the constant path /user/ prepended to it.
    • /system the system guardian.

    The names of these built-in actors contain guardian because these are supervising every actor living as a child of them, i.e. under their path. We will explain supervision in more detail, all you need to know now is that every unhandled failure from actors bubbles up to their parent that, in turn, can decide how to handle this failure. These predefined actors are guardians in the sense that they are the final lines of defense, where all unhandled failures from user, or system, actors end up.

    Does the root guardian (the root path /) have a parent? As it turns out, it has. This special entity is called the "Bubble-Walker". This special entity is invisible for the user and only has uses internally.

    Structure of an IActorRef and Paths of Actors

    The easiest way to see this in action is to simply print IActorRef instances. In this small experiment, we print the reference of the first actor we create and then we create a child of this actor, and print its reference. We have already created actors with System.ActorOf(), which creates an actor under /user directly. We call this kind of actors top level, even though in practice they are not on the top of the hierarchy, only on the top of the user defined hierarchy. Since in practice we usually concern ourselves about actors under /user this is still a convenient terminology, and we will stick to it.

    Creating a non-top-level actor is possible from any actor, by invoking Context.ActorOf() which has the exact same signature as its top-level counterpart. This is how it looks like in practice:

    public class PrintMyActorRefActor : UntypedActor
    {
        protected override void OnReceive(object message)
        {
            switch (message)
            {
                case "printit":
                    IActorRef secondRef = Context.ActorOf(Props.Empty, "second-actor");
                    Console.WriteLine($"Second: {secondRef}");
                    break;
            }
        }
    }
    
    var firstRef = Sys.ActorOf(Props.Create<PrintMyActorRefActor>(), "first-actor");
    Console.WriteLine($"First: {firstRef}");
    firstRef.Tell("printit", ActorRefs.NoSender);
    

    We see that the following two lines are printed

    First : Actor[akka://testSystem/user/first-actor#1053618476]
    Second: Actor[akka://testSystem/user/first-actor/second-actor#1544706041]
    

    First, we notice that all of the paths start with akka://testSystem/. Since all actor references are valid URLs, there is a protocol field needed, which is akka:// in the case of actors. Then, just like on the World Wide Web, the system is identified. In our case, this is testSystem, but could be any other name (if remote communication between multiple systems is enabled this name is the hostname of the system so other systems can find it on the network). Our two actors, as we have discussed before, live under user, and form a hierarchy:

    • akka://testSystem/user/first-actor is the first actor we created, which lives directly under the user guardian, /user
    • akka://testSystem/user/first-actor/second-actor is the second actor we created, using Context.ActorOf. As we see it lives directly under the first actor.

    The last part of the actor reference, like #1053618476 is a unique identifier of the actor living under the path. This is usually not something the user needs to be concerned with, and we leave the discussion of this field for later.

    Hierarchy and Lifecycle of Actors

    We have so far seen that actors are organized into a strict hierarchy. This hierarchy consists of a predefined upper layer of three actors (the root guardian, the user guardian, and the system guardian), thereafter the user created top-level actors (those directly living under /user) and the children of those. We now understand what the hierarchy looks like, but there are some nagging unanswered questions: Why do we need this hierarchy? What is it used for?

    The first use of the hierarchy is to manage the lifecycle of actors. Actors pop into existence when created, then later, at user requests, they are stopped. Whenever an actor is stopped, all of its children are recursively stopped too. This is a very useful property and greatly simplifies cleaning up resources and avoiding resource leaks (like open sockets files, etc.). In fact, one of the overlooked difficulties when dealing with low-level multi-threaded code is the lifecycle management of various concurrent resources.

    Stopping an actor can be done by calling Context.Stop(actorRef). It is considered a bad practice to stop arbitrary actors this way. The recommended pattern is to call Context.Stop(self) inside an actor to stop itself, usually as a response to some user defined stop message or when the actor is done with its job.

    The actor API exposes many lifecycle hooks that the actor implementation can override. The most commonly used are PreStart() and PostStop().

    • PreStart() is invoked after the actor has started but before it processes its first message.
    • PostStop() is invoked just before the actor stops. No messages are processed after this point.

    Again, we can try out all this with a simple experiment:

    public class StartStopActor1 : UntypedActor
    {
        protected override void PreStart()
        {
            Console.WriteLine("first started");
            Context.ActorOf(Props.Create<StartStopActor2>(), "second");
        }
    
        protected override void PostStop() => Console.WriteLine("first stopped");
    
        protected override void OnReceive(object message)
        {
            switch (message)
            {
                case "stop":
                    Context.Stop(Self);
                    break;
            }
        }
    }
    
    public class StartStopActor2 : UntypedActor
    {
        protected override void PreStart() => Console.WriteLine("second started");
        protected override void PostStop() => Console.WriteLine("second stopped");
    
        protected override void OnReceive(object message)
        {
        }
    }
    
    var first = Sys.ActorOf(Props.Create<StartStopActor1>(), "first");
    first.Tell("stop");
    

    After running it, we get the output

    first started
    second started
    second stopped
    first stopped
    

    We see that when we stopped actor first it recursively stopped actor second and thereafter it stopped itself. This ordering is strict, all PostStop() hooks of the children are called before the PostStop() hook of the parent is called.

    The family of these lifecycle hooks is rich, and we recommend reading the actor lifecycle section of the reference for all details.

    Hierarchy and Failure Handling (Supervision)

    Parents and children are not only connected by their lifecycles. Whenever an actor fails (throws an exception or an unhandled exception bubbles out from receive) it is temporarily suspended. The failure information is propagated to the parent, which decides how to handle the exception caused by the child actor. The default supervisor strategy is to stop and restart the child. If you don't change the default strategy all failures result in a restart. We won't change the default strategy in this simple experiment:

    public class SupervisingActor : UntypedActor
    {
        private IActorRef child = Context.ActorOf(Props.Create<SupervisedActor>(), "supervised-actor");
    
        protected override void OnReceive(object message)
        {
            switch (message)
            {
                case "failChild":
                    child.Tell("fail");
                    break;
            }
        }
    }
    
    public class SupervisedActor : UntypedActor
    {
        protected override void PreStart() => Console.WriteLine("supervised actor started");
        protected override void PostStop() => Console.WriteLine("supervised actor stopped");
    
        protected override void OnReceive(object message)
        {
            switch (message)
            {
                case "fail":
                    Console.WriteLine("supervised actor fails now");
                    throw new Exception("I failed!");
            }
        }
    }
    
    var supervisingActor = Sys.ActorOf(Props.Create<SupervisingActor>(), "supervising-actor");
    supervisingActor.Tell("failChild");
    

    After running the snippet, we see the following output on the console:

    supervised actor started
    supervised actor fails now
    supervised actor stopped
    supervised actor started
    [ERROR][05.06.2017 13:34:50][Thread 0003][akka://testSystem/user/supervising-actor/supervised-actor] I failed!
    Cause: System.Exception: I failed!
       at Tutorials.Tutorial1.SupervisedActor.OnReceive(Object message)
       at Akka.Actor.UntypedActor.Receive(Object message)
       at Akka.Actor.ActorBase.AroundReceive(Receive receive, Object message)
       at Akka.Actor.ActorCell.ReceiveMessage(Object message)
       at Akka.Actor.ActorCell.Invoke(Envelope envelope)
    

    We see that after failure the actor is stopped and immediately started. We also see a log entry reporting the exception that was handled, in this case, our test exception. In this example we use PreStart() and PostStop() hooks which are the default to be called after and before restarts, so we cannot distinguish from inside the actor if it was started for the first time or restarted. This is usually the right thing to do, the purpose of the restart is to set the actor in a known-good state, which usually means a clean starting stage. What actually happens though is that the PreRestart() and PostRestart() methods are called which, if not overridden, by default delegate to PostStop() and PreStart() respectively. You can experiment with overriding these additional methods and see how the output changes.

    For the impatient, we also recommend looking into the supervision reference page for more in-depth details.

    The First Actor

    Actors are organized into a strict tree, where the lifecycle of every child is tied to the parent and where parents are responsible for deciding the fate of failed children. At first, it might not be evident how to map our problem to such a tree, but in practice, this is easier than it looks. All we need to do is to rewrite our architecture diagram that contained nested boxes into a tree:

    actor tree diagram of the architecture

    In simple terms, every component manages the lifecycle of the sub-components. No sub-component can outlive the parent component. This is exactly how the actor hierarchy works. Furthermore, it is desirable that a component handles the failure of its sub-components. Together, these two desirable properties lead to the conclusion that the "contained-in" relationship of components should be mapped to the "children-of" relationship of actors.

    The remaining question is how to map the top-level components to actors. It might be tempting to create the actors representing the main components as top-level actors. We instead, recommend creating an explicit component that represents the whole application. In other words, we will have a single top-level actor in our actor system and have the main components as children of this actor.

    The first actor happens to be rather simple now, as we have not implemented any of the components yet. What is new is that we have dropped using Console.WriteLine() and instead use ILoggingAdapter which allows us to use the logging facility built into Akka.NET directly. Furthermore, we are using a recommended pattern for creating actors; define a static Props() method in the the actor:

    public class IotSupervisor : UntypedActor
    {
        public ILoggingAdapter Log { get; } = Context.GetLogger();
    
        protected override void PreStart() => Log.Info("IoT Application started");
        protected override void PostStop() => Log.Info("IoT Application stopped");
    
        // No need to handle any messages
        protected override void OnReceive(object message)
        {
        }
    
        public static Props Props() => Akka.Actor.Props.Create<IotSupervisor>();
    }
    

    All we need now is to tie this up with a class with the main entry point:

    public class IotApp
    {
        public static void Init()
        {
            using (var system = ActorSystem.Create("iot-system"))
            {
                // Create top level supervisor
                var supervisor = system.ActorOf(IotSupervisor.Props(), "iot-supervisor");
                // Exit the system after ENTER is pressed
                Console.ReadLine();
            }
        }
    }
    

    This application does very little for now, but we have the first actor in place and we are ready to extend it further.

    What Is Next?

    In the following chapters we will grow the application step-by-step:

    1. We will create the representation for a device
    2. We create the device management component
    3. We add query capabilities to device groups
    In this article
    • githubEdit this page
    Back to top
    Contribute
    • Project Chat
    • Discussion Forum
    • Source Code
    Support
    • Akka.NET Support Plans
    • Akka.NET Observability Tools
    • Akka.NET Training & Consulting
    Maintained By
    • Petabridge - The Akka.NET Company
    • Learn Akka.NET