2

I am brand new to Akka (Java lib, v2.3.9). I am trying to follow the supervisor hierarchy best practices, but since this is my first Akka app, am hitting a mental barrier somewhere.

In my first ever Akka app (really a library intended for reuse across multiple apps), input from the outside world manifests itself as a Process message that is passed to an actor. Developers using my app will provide a text-based config file that, ultimately, configures which actors get sent Process instances, and which do not. In other words, say these are my actor classes:

// Groovy pseudo-code
class Process {
    private final Input input

    Process(Input input) {
        super()
        this.input = deepClone(input)
    }

    Input getInput() {
        deepClone(this.input)
    }
}

class StormTrooper extends UntypedActor {
    @Override
    void onReceive(Object message) {
        if(message instanceof Process) {
            // Process the message like a Storm Trooper would.
        }
    }
}

class DarthVader extends UntypedActor {
    @Override
    void onReceive(Object message) {
        if(message instanceof Process) {
            // Process the message like Darth Vader would.
        }
    }
}

class Emperor extends UntypedActor {
    @Override
    void onReceive(Object message) {
        if(message instanceof Process) {
            // Process the message like the Emperor would.
        }
    }
}

// myapp-config.json -> where the actors are configured, along with other
// app-specific configs
{
    "fizzbuzz": "true",
    "isYosemite": "false",
    "borderColor": "red",
    "processors": [
        "StormTrooper",
        "Emperor"
    ]
}

As you can see in the config file, only StormTrooper and Emperor were selected to receive Process messages. This ultimately results with zero (0) DarthVader actors being created. It is also my intention that this would result with a Set<ActorRef> being made available to the application that is populated with StormTrooper and Emperor like so:

class SomeApp {
    SomeAppConfig config

    static void main(String[] args) {
        String configFileUrl = args[0] // Nevermind this horrible code

        // Pretend here that configFileUrl is a valid path to
        // myapp-config.json.

        SomeApp app = new SomeApp(configFileUrl)
        app.run()
    }

    SomeApp(String url) {
        super()

        config = new SomeAppConfig(url)
    }

    void run() {
        // Since the config file only specifies StormTrooper and
        // Emperor as viable processors, the set only contains instances of
        // these ActorRef types.
        Set<ActorRef> processors = config.loadProcessors()
        ActorSystem actorSystem = config.getActorSystem()

        while(true) {
            Input input = scanForInput()
            Process process = new Process(input)

            // Notify each config-driven processor about the
            // new input we've received that they need to process.
            processors.each {
                it.tell(process, Props.self()) // This isn't correct btw
            }
        }
    }
}

So, as you can (hopefully) see, we have all these actors (in reality, many dozens of UntypedActor impls) that handle Process messages (which, in turn, capture Input from some source). As to which Actors are even alive/online to handle these Process messages are entirely configuration-driven. Finally, every time the app receives an Input, it is injected into a Process message, and that Process message is sent to all configured/living actors.

With this as the given backstory/setup, I am unable to identify what the "actor/supervisor hierarchy" needs to be. It seems like in my use case, all actors are truly equals, with no supervisory structure between them. StormTrooper simply receives a Process message if that type of actor was configured to exist. Same for the other actor subclasses.

Am I completely missing something here? How do I define a supervisory hierarchy (for fault tolerance purposes) if all actors are equal and the hierarchy is intrinsically "flat"/horizontal?

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
smeeb
  • 27,777
  • 57
  • 250
  • 447
  • 1
    To borrow a though process from Viktor Klang, think about how you would do it if you only had regular people, not computers, then imagine each person as an actor. Which people supervise which other people? – Ryan Apr 21 '15 at 17:01
  • Thanks @Ryan (+1) - if I had actual people as actors, instead of computers, I would have a holding cell with `StormTrooper`, `DarthVader` and the `Emperor` inside of it. When someone wanted to send a message to the inhabitants of this holding cell, they would have to write down the same message on a piece of paper, one paper per person. If they wished to send a message to both `StormTrooper` and `Emperor`, they would write down the exact same message on two pieces of paper. They would then hand me all/any papers, and I would deliver the message to applicable parties. – smeeb Apr 21 '15 at 17:06
  • In other words, there's still no supervisor hierarchy here. :-) – smeeb Apr 21 '15 at 17:06

2 Answers2

2

If you want to instantiate no more than one instance for every your actor - you may want to have SenatorPalpatine to supervise those three. If you may have let's say more than one StormTrooper - you may want to have JangoFett actor responsible for creating (and maybe killing) them, some router is also good option (it will supervise them automatically). This will also give you an ability to restart all troopers if one fails (OneForAllStrategy), ability to broadcast, hold some common statistic etc.

Example (pseudo-Scala) with routers:

//application.conf
akka.actor.deployment {
  /palpatine/vader {
    router = broadcast-pool
    nr-of-instances = 1
  }
  /palpatine/troopers {
    router = broadcast-pool
    nr-of-instances = 10
  }
}

class Palpatine extends Actor {
    import context._

    val troopers = actorOf(FromConfig.props(Props[Trooper], 
"troopers").withSupervisorStrategy(strategy) //`strategy` is strategy for troopers

    val vader = actorOf(FromConfig.props(Props[Vader]), "vader")

    override val supervisorStrategy = OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1) //stategy for Palpatine's children (routers itself)

    val strategy = OneForOneStrategy(maxNrOfRetries = 100, withinTimeRange = 1) //stategy for troopers

    def receive = {
         case p@Process => troopers ! p; vader ! p
         case t@Terminted => println(t)
    }
 }

That creates broadcast pools based on standard akka-config. I also shown that you can customize supervision strategies for them separately.

If you want some of actors to ignore message by some reason - just implement this logic inside actor, like:

class Vader extends Actor {
    def receive {
        case p@Process => ...
        case Ignore => context.become(ignore) //changes message handler to `ignore`
    }


    def ignore = {
        case x => println("Ignored message " + x)
        case UnIgnore => context.become(process)//changes message handler back
    }

}

This will configure ignore/unignore dynamically (otherwise it's just a simple if). You may send Ignore message to actors based on some config:

val listOfIgnorantPathes = readFromSomeConfig()
context.actorSelection(listOfIgnoredPathes) ! Ignore

You can also create broadcaster for palpatine in the same way as trooper's router (just use groups instead of pools), if you want to control heterogenous broadcast from config:

akka.actor.deployment {
  ... //vader, troopers configuration

  /palpatine/broadcaster {
    router = broadcast-group
    routees.paths = ["/palpatine/vader", "/palpatine/troopers"]
  }
}

class Palpatine extends Actor {
   ... //vader, troopers definitions

   val broadcaster = actorOf(FromConfig.props(), "broadcaster")

   def receive = {
     case p@Process => broadcaster ! p
   }
}

Just exclude vader from routees.paths to make him not receiving Process messages.

P.S. Actors are never alone - there is always Guardian Actor (see The Top-Level Supervisors), which will shut down the whole system in case of exception. So eitherway SenatorPalpatine may really become your rescue.

P.S.2 context.actorSelection("palpatine/*") actually alows you to send message to all children (as an alternative to broadcast pools and groups), so you don't need to have a set of them inside.

dk14
  • 22,206
  • 4
  • 51
  • 88
  • Thanks @dk14 (+1) - so what might `SenatorPalpatine` look like, code-wise? Would he just contain `Set` (driven by the JSON configuration file), loop through that set and send `Process` messages to all the actors in the list? – smeeb Apr 23 '15 at 15:01
  • 1
    no, you don't need `Set` here if you want to brodcast - `context.actorSelection("../*")` ! msg (from palpatine) or `system.actorSelection("palpatine/*")` ! msg` will do the job (in Scala). – dk14 Apr 23 '15 at 15:03
  • Thanks @dk14 (+1 again) - however I'm still not seeing the "forest through the tree". For the bounty, could I be so bold as to see a simple pseudo-code example that shows: (1) `SenatorPalpatine` supervising the other actor types, and (2) `SomeApp#run` broadcasting `Process` messages to all **configured** (remember, perhaps only *some* of the actors should be receiving `Process` messages, perhaps some are configured to not receive them) to receive them? Thanks again so much for the help! – smeeb Apr 23 '15 at 15:07
  • 1
    @smeeb I've updated with Scala-examples, this solution is based on routers and akka-config, instead of custom configuration – dk14 Apr 23 '15 at 15:36
  • Thanks yet again @dk14 (+1) - I think we're *almost* there! For the most part, now I understand how `Palpatine` would work, but I'm still unsure of the integration between either `SomeApp` and `Palpatine`, or between `SomeApp` and the actor system as a whole. When `SomeApp#run` generates a new `Process` message, does it invoke `Palpatine` (who in turn broadcasts the message to all configured actors)? Or is something else going on? Thanks again! – smeeb Apr 23 '15 at 16:39
  • 1
    it creates one `Palpatine` instance with name "palpatine" and sends a `Process` message to it. – dk14 Apr 23 '15 at 17:06
  • 1
    Btw, `Palpatine` may not be same as `Emperor` here, actually `Emperor` may break the whole metaphore if you think that empire may live without him or have several emperors :). So it's just another child of `Palpatine` then (from SW-perspective should be the opposite - I know :), just forgot about him ) – dk14 Apr 23 '15 at 17:11
1

Based on your comment, you would still want a Master actor to duplicate and distribute Processes. Conceptually, you wouldn't have the user (or whatever is generating your input) provide the same input once per actor. They would provide the message only once, and you (or the Master actor) would then duplicate the message as necessary and send it to each of its appropriate child actors.

As discussed in dk14's answer, this approach has the added benefit of increased fault tolerance.

Community
  • 1
  • 1
Luke Willis
  • 8,429
  • 4
  • 46
  • 79
  • Thanks @Luke Willis (+1) - please see my last comment requesting a simple code snippet from dk14 below - I have the same question for you! – smeeb Apr 23 '15 at 15:08