Reputation: 6242
I have a PersistentActor
which, for a given event, can mutate his state and/or his behavior. For persisting the state there's no problem, but it turns out that I need to persist the behavior also as part of the state, otherwise it won't be correctly initialized in case of a failure recovery from a snapshot, since the snapshot only carries the state and not the behavior. How to achieve this correctly?
Currently, what I do is to save a tuple of the state and the behavior, but I don't know if this is the correct way to do it. I'm open to any kind of suggestions and ideas. FWIW, the use case of this kind of actor is as an entry for cluster sharding, where I need each entry to go through several states using (become/unbecome) and persist the state of the entries as well as the behavior they were in. Thanks a lot.
Here is the code illustrating the problem:
class FooActor extends PersistentActor with ActorLogging {
import FooActor._
var state: List[String] = Nil
def updateState(e: FooEvent): Unit = e match {
case Initialized ⇒ context become initialized
case Uninitialized ⇒ context become uninitialized
case Fooed(data) ⇒ state = data :: state
}
def uninitialized: Receive = {
case Init ⇒ persist(Initialized)(updateState)
}
def initialized: Receive = {
case Foo(data) ⇒ persist(Fooed(data))(updateState)
case Uninit ⇒ persist(Uninitialized)(updateState)
case Snap ⇒ saveSnapshot((state, receiveCommand)) // how to persist current behavior also?
}
def receiveRecover: Receive = {
case e: FooEvent ⇒ updateState(e)
// here, if I don't persist the behavior also, when the state is
// recovered, the actor would be in the uninitialized behavior and
// thus will not process any Foo commands
case SnapshotOffer(_, (_state: List[String], behavior: Receive)) ⇒
state = _state
context become behavior
}
def receiveCommand: Receive = uninitialized
val persistenceId = "foo-pid"
}
object FooActor {
case object Init
case object Uninit
case object Snap
case class Foo(data: String)
trait FooEvent
case object Initialized extends FooEvent
case object Uninitialized extends FooEvent
case class Fooed(data: String) extends FooEvent
}
Upvotes: 0
Views: 891
Reputation: 4652
What you are doing looks good, just I think in general you should first change your behavior during recover and then update the state (it's not the case here, but there maybe some validation constraints in general on your state).
The only other option I can think of is to override the onshutdown handler and guarantee that you unitialized yourself and save a snapshot before going down, as well as an init message. In this way you are guaranteed that on respawn you are uninitialized, and will receive the init message. Something like
onShutdown {
context become uninit
saveSnapshot(state)
persist(Init)
}
On restart, your uninit snapshot would receive first of all an init message, and then whatever comes next.
Though I'm not sure it's much better, you should be able to avoid storing the behavior, but then you'd have to be careful never to die without doing that.
Upvotes: 1