I have a class that offers different kinds of Source
s 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.
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, _))
}
}