Stash
Dependency
To use Akka Actor Typed, you must add the following dependency in your project:
Introduction
Stashing enables an actor to temporarily buffer all or some messages that cannot or should not be handled using the actor’s current behavior.
A typical example when this is useful is if the actor has too load some initial state or initialize some resources before it can accept the first real message. Another example is when the actor is waiting for something to complete before processing next message.
Let’s illustrate these two with an example. It’s an actor that is used like a single access point to a value stored in a database. When it’s started it loads current state from the database, and while waiting for that initial value all incoming messages are stashed.
When a new state is saved in the database it also stashes incoming messages to make the processing sequential, one after the other without multiple pending writes.
- Scala
-
source
import akka.actor.typed.scaladsl.StashBuffer trait DB { def save(id: String, value: String): Future[Done] def load(id: String): Future[String] } object DataAccess { trait Command final case class Save(value: String, replyTo: ActorRef[Done]) extends Command final case class Get(replyTo: ActorRef[String]) extends Command private final case class InitialState(value: String) extends Command private final case object SaveSuccess extends Command private final case class DBError(cause: Throwable) extends Command def behavior(id: String, db: DB): Behavior[Command] = Behaviors.setup[Command] { context => val buffer = StashBuffer[Command](capacity = 100) def init(): Behavior[Command] = Behaviors.receive[Command] { (context, message) => message match { case InitialState(value) => // now we are ready to handle stashed messages if any buffer.unstashAll(context, active(value)) case DBError(cause) => throw cause case other => // stash all other messages for later processing buffer.stash(other) Behaviors.same } } def active(state: String): Behavior[Command] = Behaviors.receive { (context, message) => message match { case Get(replyTo) => replyTo ! state Behaviors.same case Save(value, replyTo) => context.pipeToSelf(db.save(id, value)) { case Success(_) => SaveSuccess case Failure(cause) => DBError(cause) } saving(value, replyTo) } } def saving(state: String, replyTo: ActorRef[Done]): Behavior[Command] = Behaviors.receive[Command] { (context, message) => message match { case SaveSuccess => replyTo ! Done buffer.unstashAll(context, active(state)) case DBError(cause) => throw cause case other => buffer.stash(other) Behaviors.same } } context.pipeToSelf(db.load(id)) { case Success(value) => InitialState(value) case Failure(cause) => DBError(cause) } init() } }
- Java
One important thing to be aware of is that the StashBuffer
is a buffer and stashed messages will be kept in memory until they are unstashed (or the actor is stopped and garbage collected). It’s recommended to avoid stashing too many messages to avoid too much memory usage and even risking OutOfMemoryError
if many actors are stashing many messages. Therefore the StashBuffer
is bounded and the capacity
of how many messages it can hold must be specified when it’s created.
If you try to stash more messages than the capacity
a StashOverflowException
will be thrown. You can use StashBuffer.isFull
before stashing a message to avoid that and take other actions, such as dropping the message.
When unstashing the buffered messages by calling unstashAll
the messages will be processed sequentially in the order they were added and all are processed unless an exception is thrown. The actor is unresponsive to other new messages until unstashAll
is completed. That is another reason for keeping the number of stashed messages low. Actors that hog the message processing thread for too long can result in starvation of other actors.
That can be mitigated by using the StashBuffer.unstash
with numberOfMessages
parameter and then send a message to context.self
before continuing unstashing more. That means that other new messages may arrive in-between and those must be stashed to keep the original order of messages. It becomes more complicated, so better keep the number of stashed messages low.