Search code examples
scalaakkaakka-typed

Looking for an abstraction that allows a class to spawn actors


I have a class that offers different kinds of Sources to it's clients. When a Source is run, an actor should be spawned that feeds new entries into the respective stream. So my class needs to be able to spawn actors. I know of two ways to do this: Using the ActorContext of another actor or using the ActorSystem. Is there a common abstraction for the ability to spawn new actors so I could inject a helper into my class that just allows it to spawn actors if required regardless of how it is done?

I created an ActorSpawner interface for this purpose and it worked pretty well so far:

trait ActorSpawner {

  def spawn[T](behavior: Behavior[T]): ActorRef[T]

  def spawn[T](behavior: Behavior[T], name: String): ActorRef[T]

} 

However, since I upgraded to Akka 2.6 I frequently get these error messages:

ERROR akka.actor.SupervisorStrategy - Unsupported access to ActorContext from the outside of Actor[akka://...]. No message is currently processed by the actor, but ActorContext was called from Thread[...]

It seems this wasn't a problem before the upgrade but now I'm wondering whether what I was doing was advisable anyway or if this is kind of an anti-pattern.


Solution

  • In Typed, with the exception of the guardian actor (the one created when creating the ActorSystem), actors can only be spawned by other actors.

    There are still plenty of cases where code executing outside of an actor wants to spawn an actor. For this, you can send a message to an actor and have that actor spawn an actor with the desired behavior.

    If you have an actor whose sole purpose is spawning other actors, Akka includes the SpawnProtocol, which is compatible with the ask pattern:

    trait ActorSpawner {
      def spawn[T](behavior: Behavior[T], name: String): Future[ActorRef[T]]
    
      def spawn[T](behavior: Behavior[T]): Future[ActorRef[T]] = spawn(behavior, "")
    }
    
    def spawnerFor(spawningActor: ActorRef[SpawnProtocol.Spawn])(implicit system: ActorSystem[Nothing]): ActorSpawner = {
      import system.executionContext
      import akka.actor.typed.scaladsl.AskPattern._
    
      implicit val timeout = Timeout(10.seconds)  // or whatever, can also make this an arg
    
      new ActorSpawner {
        def spawn[T](behavior: Behavior[T], name: String): Future[ActorRef[T]] =
          spawningActor.ask(SpawnProtocol.Spawn(behavior, name, Props.empty, _))
      }
    }
    

    If your guardian actor happens to only spawn other actors on demand, it can implement SpawnProtocol, in which case the ActorSystem is itself an ActorRef[SpawnProtocol.Spawn], so that could be further simplified to

    def spawnerFor(system: ActorSystem[SpawnProtocol.Spawn]): ActorSpawner = {
      import system.executionContext
      import akka.actor.typed.scaladsl.AskPattern._
    
      implicit val timeout = Timeout(10.seconds)
      implicit val scheduler = system.scheduler
    
      new ActorSpawner {
        def spawn[T](behavior: Behavior[T], name: String): Future[ActorRef[T]] =
          system.ask(SpawnProtocol.Spawn(behavior, name, Props.empty, _))
      }
    }