jhegedus
jhegedus

Reputation: 20653

PoisonPill causes dead letters with persistent actor

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

Answers (1)

Cyrille Corpet
Cyrille Corpet

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

Related Questions