Search code examples
scalaakkaactor

Why Akka hangs while spawning child actor


I am trying to implement an A/B Testing mechanism using Akka actors (scala 2.12.8, akka-actor-typed 2.6.1)

I designed it as:

  • 1 A/B Testing root actor
  • 2 Variant actors, children of the A/B Testing actor

When the A/B Testing actor will receive a message, it will choose one variant and forward the message to it.

However until now, I didn't managed to make the root actor spawn its children. Here is my code.

import akka.actor.typed.{ActorRef, ActorSystem, Behavior}
import akka.actor.typed.scaladsl.{AbstractBehavior, ActorContext, Behaviors}

import scala.util.Random

// My variants
object Ranker {
  sealed trait Command
  final case class Rank() extends Command
  def apply(rank: Int): Behavior[Command] = Behaviors.setup { context => new Ranker(context, rank) }
}

class Ranker(context: ActorContext[Ranker.Command], rank: Int) extends AbstractBehavior[Ranker.Command](context) {
  override def onMessage(msg: Ranker.Command): Behavior[Ranker.Command] = {
    println(rank)
    this
  }
}

// The root A/B Testing actor
object ABTester {
  def apply(): Behavior[Ranker.Command] = Behaviors.setup { context => new ABTester(context) }
}

class ABTester(context: ActorContext[Ranker.Command]) extends AbstractBehavior[Ranker.Command](context) {
  val rng = new Random()
  // Spawn children actors
  val rankers: Seq[ActorRef[Ranker.Command]] = Seq(Ranker(1), Ranker(2)).zipWithIndex
    .map { case (b, i) =>
      println(s"Spawning ranker $i")
      val actorRef = context.spawn(b, s"Ranker $i")
      println(s"Spawning ranker $i: Done")
      actorRef
    }

  override def onMessage(msg: Ranker.Command): Behavior[Ranker.Command] = {
    rankers(rng.nextInt(rankers.size)) ! msg
    this
  }
}

// Application entry point
object Main {
  def main(args: Array[String]): Unit = {
    val actor = ActorSystem(ABTester(), "ABTester")
    actor ! Ranker.Rank()
    Thread.sleep(5000)
  }
}

Running this sample will print:

Spawning ranker 0

but that's all, as if the line 31 (val actorRef = context.spawn(b, s"Ranker $i")) never returns...

Did I miss something about child actors spawning?


Solution

  • TL;DR

    Actor paths MUST:
      not start with `$`,
      include only ASCII letters
      and can only contain these special characters: -_.*$+:@&=,!~';.`
    

    I finally spotted the error!

    I didn't properly set up the logging and thus the error message never appear. After adding the slf4j-simple dependency, this is what happens:

    [ABTester-akka.actor.default-dispatcher-3] INFO akka.event.slf4j.Slf4jLogger - Slf4jLogger started
    SLF4J: A number (1) of logging calls during the initialization phase have been intercepted and are
    SLF4J: now being replayed. These are subject to the filtering rules of the underlying logging system.
    SLF4J: See also http://www.slf4j.org/codes.html#replay
    [ABTester-akka.actor.default-dispatcher-3] INFO ABTester - Spawning ranker 0
    [ABTester-akka.actor.default-dispatcher-3] ERROR akka.actor.LocalActorRefProvider(akka://ABTester) - guardian failed, shutting down system
    akka.actor.InvalidActorNameException: Invalid actor path element [Ranker 0], illegal character [ ] at position: 6. Actor paths MUST: not start with `$`, include only ASCII letters and can only contain these special characters: -_.*$+:@&=,!~';.
        at akka.actor.ActorPath$.validatePathElement(ActorPath.scala:98)
        at akka.actor.ActorPath$.validatePathElement(ActorPath.scala:76)
        at akka.actor.dungeon.Children.checkName(Children.scala:248)
        at akka.actor.dungeon.Children.actorOf(Children.scala:47)
        at akka.actor.dungeon.Children.actorOf$(Children.scala:46)
        at akka.actor.ActorCell.actorOf(ActorCell.scala:408)
        at akka.actor.typed.internal.adapter.ActorRefFactoryAdapter$.spawn(ActorRefFactoryAdapter.scala:41)
        at akka.actor.typed.internal.adapter.ActorContextAdapter.spawn(ActorContextAdapter.scala:66)
        at ABTester.$anonfun$rankers$1(ABTester.scala:31)
        [...]
    

    The actor path (passed to the spawn method) must not contain any space.

    Changing from Ranker $i to Ranker_$i result in:

    [ABTester-akka.actor.default-dispatcher-3] INFO akka.event.slf4j.Slf4jLogger - Slf4jLogger started
    [ABTester-akka.actor.default-dispatcher-6] INFO ABTester - Spawning ranker 0
    [ABTester-akka.actor.default-dispatcher-6] INFO ABTester - Spawning ranker 0: Done
    [ABTester-akka.actor.default-dispatcher-6] INFO ABTester - Spawning ranker 1
    [ABTester-akka.actor.default-dispatcher-6] INFO ABTester - Spawning ranker 1: Done
    [ABTester-akka.actor.default-dispatcher-3] INFO Ranker - rank: 2