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:
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?
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