Search code examples
scalaakkaagent-based-modeling

Dead letters encountered in Akka when routing between sibling Actors


I am trying to build an agent based model where part of the model involves agents broadcasting decisions. First I initialize exactly four actors with a tracking parent, then I make them each "declare" their decisions in a specific order.

I expect each to update their parameters with each time a sibling declares its decision, but when I run the code shown below, I get logs about dead letters being encountered. How do I make sure all actors run in order, receive all declarations from siblings and do not terminate until they receive a message to declare themselves?

The main method:

import Member._

  val system: ActorSystem = ActorSystem("Abilene0")

  try {
    val groupMembers = Set("father", "mother", "wife", "husband")
    val group: ActorRef = system.actorOf(Group.props(groupMembers), "group")

    val father = system.actorOf(Member.props(group, generate(groupMembers)), "father")
    val mother = system.actorOf(Member.props(group, generate(groupMembers)), "mother")
    val wife = system.actorOf(Member.props(group, generate(groupMembers)), "wife")
    val husband = system.actorOf(Member.props(group, generate(groupMembers)), "husband")

    father ! Declare
    wife ! Declare
    husband ! Declare
    mother ! Declare

  } finally {
    system.terminate()
  }

Solution

  • By looking at your code, its obvious that actor messages are going to delivered to deadletters. In your code, you are invoking system.terminate() in finally block of your try-catch-finally statement, that means, actor system termination is going to occur immediately after your try-block that will immediately stop all the actors. However, you should know that Actors communicate using asynchronous messages. That means, even after your try-block code completed, there can be various background task being executed in your actors until all the actors stopped.

    Let me create you a scenario:

    class Handler extends Actor with ActorLogging{
      override def receive: Receive = {
        case "START" =>
          println("Test Started.")
      }
    }
    
    class Tester(handler: ActorRef) extends Actor with ActorLogging{
      override def receive: Receive = {
        case "TEST" =>
          println("Test started.")
          // Here, when handler actor stops when system.terminate() occur, "START" message
          // will be no longer delivered to actor handler instead delivered to deadletters.
          1 to 1000 foreach ( _ => {
            handler ! "START"
          })
      }
    }
    
    val system: ActorSystem = ActorSystem("test")
    val handler = system.actorOf(Props(new Handler), "handler")
    val tester = system.actorOf(Props(new Tester(handler)), "tester")
    try {
      tester ! "TEST"
    } finally {
      system.terminate()
    }
    

    In above code, when system.terminate() is invoked, handler actor is stopped and then tester actor is stopped. However, before tester actor is stopped, there can be still "START" message being sent to handler actor. However, handler actor is already stopped, this "START" message will be no longer delivered to handler and hence delivered to deadletters.

    Therefore, in your code, upon system.terminate() executed, all your actors - group, father, mother, wife and husband are immediately stopped and hence any further messages sent to these actors mailboxes are being delivered to synthetic actor deadletters.

    How do I make sure all actors run in order, receive all declarations from siblings and do not terminate until they receive a message to declare themselves?

    For this you can pass PoisonPill message to each actor explicitly which will stop the actor only after all the messages in its mailbox are processed.