Reputation: 20653
I am testing a persistent actor (that stores an entity) with test kit:
val testActor = system.actorOf((IMPersistentActor.props("id1")))
testActor ! AddElementCommand(refVal)
// expectMsg(AddElementResponse(Success(refVal))) // no dead letters if I uncomment this
testActor ! PoisonPill
which gives:
Message [app.server.persistence.IMPersistentActor$AddElementCommand]
from Actor[akka://IMPersistentActorTest/system/testActor-1#182376248]
to Actor[akka://IMPersistentActorTest/user/$a#-899768724] was not delivered.
I thought that AddElementCommand(refVal)
should arrive before PoisonPill
and should be processed. How can this behaviour be explained ? Why do I get this dead letter ?
Interestingly however, if I uncomment expectMsg(AddElementResponse(Success(refVal)))
then I get no dead letters and the test passes. Why is that ?
Here is the full code :
class IMPersistentActorTest
extends TestKit(ActorSystem("IMPersistentActorTest"))
with WordSpecLike
with Matchers
with BeforeAndAfterAll
with ImplicitSender {
override
def afterAll: Unit = {
TestKit.shutdownActorSystem(system)
}
"Actor" should {
val el = Line(title = Some("bla"))
val refVal: RefVal[Line] = RefVal.makeWithNewUUID[Line](el)
"add an item to the list and preserve it after restart" in {
// val testActor = system.actorOf(Props(new IMPersistentActor("sc-000001") with RestartableActor))
val testActor = system.actorOf((IMPersistentActor.props("id1")))
testActor ! AddElementCommand(refVal)
val state: State = IMPersistentActor.addEntity(IMPersistentActor.initState, refVal)
testActor ! PoisonPill
val testActor2: ActorRef = system.actorOf((IMPersistentActor.props("id1")))
testActor2 ! GetElementsRequest
expectMsg(GetElementsResponse(state))
}
}
}
class IMPersistentActor(id: String) extends PersistentActor with ActorLogging {
private var state: State =initState
override def persistenceId: String = id
override def receiveCommand: Receive = {
case AddElementCommand(item) =>
persist(EntityAdded(item)) { evt =>
state = applyEvent(evt)
sender() ! AddElementResponse(Success(item))
}
case GetElementsRequest => sender() ! GetElementsResponse(state)
}
override def receiveRecover: Receive = {
case evt: Event => state = applyEvent(evt)
case RecoveryCompleted => log.info("Recovery completed!")
}
private def applyEvent(event: Event): State = event match {
case EntityAdded(refVal: (RefValDyn)) => addEntity(state, refVal)
}
}
object IMPersistentActor {
type State = Map[RefDyn, RefValDyn]
def initState:State=Map.empty
def addEntity(s: State, refVal: RefValDyn): State = s + (refVal.r -> refVal)
def props(id: String): Props = Props(new IMPersistentActor(id))
// protocol
case class AddElementCommand(entity: RefValDyn)
case class AddElementResponse(entity: Try[RefValDyn])
case object GetElementsRequest
case class GetElementsResponse(items: State)
// events
sealed trait Event
case class EntityAdded(entity: RefValDyn) extends Event
}
trait IMPersistentActorComm[T]
Upvotes: 1
Views: 288
Reputation: 5315
You should take a look at this part of the documentation. The short version is that persistence is handled asynchronously, so the PoisonPill
message is received before the message telling the actor that the event has been persisted.
Upvotes: 3