2015-09-15 09:24:46 +02:00
|
|
|
/**
|
2017-01-04 17:37:10 +01:00
|
|
|
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
2015-09-15 09:24:46 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
package akka.persistence
|
|
|
|
|
|
2017-12-06 00:32:04 -06:00
|
|
|
import akka.actor.SupervisorStrategy.Resume
|
|
|
|
|
import akka.actor.{ ActorRef, OneForOneStrategy, Actor, Props }
|
2015-09-15 09:24:46 +02:00
|
|
|
import akka.persistence.journal.SteppingInmemJournal
|
|
|
|
|
import akka.testkit.ImplicitSender
|
|
|
|
|
import com.typesafe.config.Config
|
|
|
|
|
import scala.concurrent.duration._
|
2016-03-18 12:11:43 +08:00
|
|
|
import scala.reflect.ClassTag
|
2015-09-15 09:24:46 +02:00
|
|
|
|
|
|
|
|
object PersistentActorStashingSpec {
|
|
|
|
|
final case class Cmd(data: Any)
|
|
|
|
|
final case class Evt(data: Any)
|
|
|
|
|
|
2016-03-18 12:11:43 +08:00
|
|
|
abstract class StashExamplePersistentActor(name: String) extends NamedPersistentActor(name) {
|
2015-09-15 09:24:46 +02:00
|
|
|
var events: List[Any] = Nil
|
|
|
|
|
var askedForDelete: Option[ActorRef] = None
|
|
|
|
|
|
|
|
|
|
val updateState: Receive = {
|
|
|
|
|
case Evt(data) ⇒ events = data :: events
|
|
|
|
|
case d @ Some(ref: ActorRef) ⇒ askedForDelete = d.asInstanceOf[Some[ActorRef]]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val commonBehavior: Receive = {
|
|
|
|
|
case "boom" ⇒ throw new TestException("boom")
|
|
|
|
|
case GetState ⇒ sender() ! events.reverse
|
|
|
|
|
}
|
2016-06-02 14:06:57 +02:00
|
|
|
|
2016-03-18 12:11:43 +08:00
|
|
|
def unstashBehavior: Receive
|
2015-09-15 09:24:46 +02:00
|
|
|
|
|
|
|
|
def receiveRecover = updateState
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-18 12:11:43 +08:00
|
|
|
class UserStashPersistentActor(name: String) extends StashExamplePersistentActor(name) {
|
2015-09-15 09:24:46 +02:00
|
|
|
var stashed = false
|
2016-06-02 14:06:57 +02:00
|
|
|
|
2016-03-18 12:11:43 +08:00
|
|
|
val receiveCommand: Receive = unstashBehavior orElse {
|
2016-06-02 14:06:57 +02:00
|
|
|
case Cmd("a") if !stashed ⇒
|
|
|
|
|
stash(); stashed = true
|
|
|
|
|
case Cmd("a") ⇒ sender() ! "a"
|
|
|
|
|
case Cmd("b") ⇒ persist(Evt("b"))(evt ⇒ sender() ! evt.data)
|
2016-03-18 12:11:43 +08:00
|
|
|
}
|
2016-06-02 14:06:57 +02:00
|
|
|
|
2016-03-18 12:11:43 +08:00
|
|
|
def unstashBehavior: Receive = {
|
2016-06-02 14:06:57 +02:00
|
|
|
case Cmd("c") ⇒ unstashAll(); sender() ! "c"
|
2016-03-18 12:11:43 +08:00
|
|
|
}
|
|
|
|
|
}
|
2016-06-02 14:06:57 +02:00
|
|
|
|
2016-03-18 12:11:43 +08:00
|
|
|
class UserStashWithinHandlerPersistentActor(name: String) extends UserStashPersistentActor(name: String) {
|
|
|
|
|
override def unstashBehavior: Receive = {
|
2016-06-02 14:06:57 +02:00
|
|
|
case Cmd("c") ⇒ persist(Evt("c")) { evt ⇒ sender() ! evt.data; unstashAll() }
|
2015-09-15 09:24:46 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-18 12:11:43 +08:00
|
|
|
class UserStashManyPersistentActor(name: String) extends StashExamplePersistentActor(name) {
|
2015-09-15 09:24:46 +02:00
|
|
|
val receiveCommand: Receive = commonBehavior orElse {
|
2016-06-02 14:06:57 +02:00
|
|
|
case Cmd("a") ⇒ persist(Evt("a")) { evt ⇒
|
2015-09-15 09:24:46 +02:00
|
|
|
updateState(evt)
|
|
|
|
|
context.become(processC)
|
|
|
|
|
}
|
|
|
|
|
case Cmd("b-1") ⇒ persist(Evt("b-1"))(updateState)
|
|
|
|
|
case Cmd("b-2") ⇒ persist(Evt("b-2"))(updateState)
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-18 12:11:43 +08:00
|
|
|
val processC: Receive = unstashBehavior orElse {
|
|
|
|
|
case other ⇒ stash()
|
|
|
|
|
}
|
2016-06-02 14:06:57 +02:00
|
|
|
|
2016-03-18 12:11:43 +08:00
|
|
|
def unstashBehavior: Receive = {
|
2015-09-15 09:24:46 +02:00
|
|
|
case Cmd("c") ⇒
|
2016-03-18 12:11:43 +08:00
|
|
|
persist(Evt("c")) { evt ⇒ updateState(evt); context.unbecome() }
|
2015-09-15 09:24:46 +02:00
|
|
|
unstashAll()
|
2016-03-18 12:11:43 +08:00
|
|
|
}
|
|
|
|
|
}
|
2016-06-02 14:06:57 +02:00
|
|
|
|
2016-03-18 12:11:43 +08:00
|
|
|
class UserStashWithinHandlerManyPersistentActor(name: String) extends UserStashManyPersistentActor(name) {
|
|
|
|
|
override def unstashBehavior: Receive = {
|
|
|
|
|
case Cmd("c") ⇒ persist(Evt("c")) { evt ⇒ updateState(evt); context.unbecome(); unstashAll() }
|
2015-09-15 09:24:46 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-18 12:11:43 +08:00
|
|
|
class UserStashFailurePersistentActor(name: String) extends StashExamplePersistentActor(name) {
|
2015-09-15 09:24:46 +02:00
|
|
|
val receiveCommand: Receive = commonBehavior orElse {
|
|
|
|
|
case Cmd(data) ⇒
|
|
|
|
|
if (data == "b-2") throw new TestException("boom")
|
2016-03-18 12:11:43 +08:00
|
|
|
persist(Evt(data)) { evt ⇒
|
|
|
|
|
updateState(evt)
|
2015-09-15 09:24:46 +02:00
|
|
|
if (data == "a") context.become(otherCommandHandler)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-18 12:11:43 +08:00
|
|
|
val otherCommandHandler: Receive = unstashBehavior orElse {
|
|
|
|
|
case other ⇒ stash()
|
|
|
|
|
}
|
2016-06-02 14:06:57 +02:00
|
|
|
|
2016-03-18 12:11:43 +08:00
|
|
|
def unstashBehavior: Receive = {
|
2015-09-15 09:24:46 +02:00
|
|
|
case Cmd("c") ⇒
|
2016-03-18 12:11:43 +08:00
|
|
|
persist(Evt("c")) { evt ⇒
|
|
|
|
|
updateState(evt)
|
2015-09-15 09:24:46 +02:00
|
|
|
context.unbecome()
|
|
|
|
|
}
|
|
|
|
|
unstashAll()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-18 12:11:43 +08:00
|
|
|
class UserStashWithinHandlerFailureCallbackPersistentActor(name: String) extends UserStashFailurePersistentActor(name) {
|
|
|
|
|
override def unstashBehavior: Receive = {
|
|
|
|
|
case Cmd("c") ⇒
|
|
|
|
|
persist(Evt("c")) { evt ⇒
|
|
|
|
|
updateState(evt)
|
|
|
|
|
context.unbecome()
|
|
|
|
|
unstashAll()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class AsyncStashingPersistentActor(name: String) extends StashExamplePersistentActor(name) {
|
2015-09-15 09:24:46 +02:00
|
|
|
var stashed = false
|
2016-06-02 14:06:57 +02:00
|
|
|
|
2016-03-18 12:11:43 +08:00
|
|
|
val receiveCommand: Receive = commonBehavior orElse unstashBehavior orElse {
|
2016-06-02 14:06:57 +02:00
|
|
|
case Cmd("a") ⇒ persistAsync(Evt("a"))(updateState)
|
|
|
|
|
case Cmd("b") if !stashed ⇒
|
|
|
|
|
stash(); stashed = true
|
|
|
|
|
case Cmd("b") ⇒ persistAsync(Evt("b"))(updateState)
|
2016-03-18 12:11:43 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override def unstashBehavior: Receive = {
|
2015-09-14 11:08:22 +02:00
|
|
|
case Cmd("c") ⇒ persistAsync(Evt("c"))(updateState); unstashAll()
|
2015-09-15 09:24:46 +02:00
|
|
|
}
|
|
|
|
|
}
|
2016-06-02 14:06:57 +02:00
|
|
|
|
2016-03-18 12:11:43 +08:00
|
|
|
class AsyncStashingWithinHandlerPersistentActor(name: String) extends AsyncStashingPersistentActor(name) {
|
|
|
|
|
override def unstashBehavior: Receive = {
|
|
|
|
|
case Cmd("c") ⇒ persistAsync(Evt("c")) { evt ⇒ updateState(evt); unstashAll() }
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-09-15 09:24:46 +02:00
|
|
|
|
2017-12-06 00:32:04 -06:00
|
|
|
class StashWithinHandlerSupervisor(target: ActorRef, name: String) extends Actor {
|
|
|
|
|
|
|
|
|
|
val child = context.actorOf(Props(classOf[StashWithinHandlerPersistentActor], name))
|
|
|
|
|
|
|
|
|
|
override val supervisorStrategy = OneForOneStrategy(loggingEnabled = false) {
|
|
|
|
|
case ex: Exception ⇒
|
|
|
|
|
target ! ex
|
|
|
|
|
Resume
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def receive = {
|
|
|
|
|
case c: Cmd ⇒ child ! c
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class StashWithinHandlerPersistentActor(name: String) extends NamedPersistentActor(name) {
|
|
|
|
|
val receiveRecover: Receive = {
|
|
|
|
|
case _ ⇒ // ignore
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def stashWithinHandler(evt: Evt) = {
|
|
|
|
|
stash()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val receiveCommand: Receive = {
|
|
|
|
|
case Cmd("a") ⇒ persist(Evt("a"))(stashWithinHandler)
|
|
|
|
|
case Cmd("b") ⇒ persistAsync(Evt("b"))(stashWithinHandler)
|
|
|
|
|
case Cmd("c") ⇒
|
|
|
|
|
persist(Evt("x")) { _ ⇒ }
|
|
|
|
|
deferAsync(Evt("c"))(stashWithinHandler)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
2015-09-15 09:24:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
abstract class PersistentActorStashingSpec(config: Config) extends PersistenceSpec(config)
|
|
|
|
|
with ImplicitSender {
|
|
|
|
|
import PersistentActorStashingSpec._
|
|
|
|
|
|
2016-06-02 14:06:57 +02:00
|
|
|
def stash[T <: NamedPersistentActor: ClassTag](): Unit = {
|
2015-09-15 09:24:46 +02:00
|
|
|
"support user stash operations" in {
|
2016-03-18 12:11:43 +08:00
|
|
|
val persistentActor = namedPersistentActor[T]
|
2015-09-15 09:24:46 +02:00
|
|
|
persistentActor ! Cmd("a")
|
|
|
|
|
persistentActor ! Cmd("b")
|
|
|
|
|
persistentActor ! Cmd("c")
|
|
|
|
|
expectMsg("b")
|
|
|
|
|
expectMsg("c")
|
|
|
|
|
expectMsg("a")
|
|
|
|
|
}
|
2016-03-18 12:11:43 +08:00
|
|
|
}
|
2015-09-15 09:24:46 +02:00
|
|
|
|
2016-06-02 14:06:57 +02:00
|
|
|
def stashWithSeveralMessages[T <: NamedPersistentActor: ClassTag](): Unit = {
|
2015-09-15 09:24:46 +02:00
|
|
|
"support user stash operations with several stashed messages" in {
|
2016-03-18 12:11:43 +08:00
|
|
|
val persistentActor = namedPersistentActor[T]
|
2015-09-15 09:24:46 +02:00
|
|
|
val n = 10
|
|
|
|
|
val cmds = 1 to n flatMap (_ ⇒ List(Cmd("a"), Cmd("b-1"), Cmd("b-2"), Cmd("c")))
|
|
|
|
|
val evts = 1 to n flatMap (_ ⇒ List("a", "c", "b-1", "b-2"))
|
|
|
|
|
|
|
|
|
|
cmds foreach (persistentActor ! _)
|
|
|
|
|
persistentActor ! GetState
|
2016-08-03 14:06:57 +02:00
|
|
|
expectMsg(evts.toList)
|
2015-09-15 09:24:46 +02:00
|
|
|
}
|
2016-03-18 12:11:43 +08:00
|
|
|
}
|
2015-09-15 09:24:46 +02:00
|
|
|
|
2016-06-02 14:06:57 +02:00
|
|
|
def stashUnderFailures[T <: NamedPersistentActor: ClassTag](): Unit = {
|
2015-09-15 09:24:46 +02:00
|
|
|
"support user stash operations under failures" in {
|
2016-03-18 12:11:43 +08:00
|
|
|
val persistentActor = namedPersistentActor[T]
|
2015-09-15 09:24:46 +02:00
|
|
|
val bs = 1 to 10 map ("b-" + _)
|
|
|
|
|
persistentActor ! Cmd("a")
|
|
|
|
|
bs foreach (persistentActor ! Cmd(_))
|
|
|
|
|
persistentActor ! Cmd("c")
|
|
|
|
|
persistentActor ! GetState
|
|
|
|
|
expectMsg(List("a", "c") ++ bs.filter(_ != "b-2"))
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-03-18 12:11:43 +08:00
|
|
|
|
|
|
|
|
"Stashing in a persistent actor" must {
|
|
|
|
|
behave like stash[UserStashPersistentActor]()
|
|
|
|
|
behave like stashWithSeveralMessages[UserStashManyPersistentActor]()
|
|
|
|
|
behave like stashUnderFailures[UserStashFailurePersistentActor]()
|
|
|
|
|
}
|
2016-06-02 14:06:57 +02:00
|
|
|
|
2016-03-18 12:11:43 +08:00
|
|
|
"Stashing(unstashAll called in handler) in a persistent actor" must {
|
|
|
|
|
behave like stash[UserStashWithinHandlerPersistentActor]()
|
|
|
|
|
behave like stashWithSeveralMessages[UserStashWithinHandlerManyPersistentActor]()
|
|
|
|
|
behave like stashUnderFailures[UserStashWithinHandlerFailureCallbackPersistentActor]()
|
|
|
|
|
}
|
2016-06-02 14:06:57 +02:00
|
|
|
|
2017-12-06 00:32:04 -06:00
|
|
|
"Stashing(stash called in handler) in a persistent actor" must {
|
|
|
|
|
"fail when calling stash in persist handler" in {
|
|
|
|
|
|
|
|
|
|
val actor = system.actorOf(Props(classOf[StashWithinHandlerSupervisor], testActor, name))
|
|
|
|
|
|
|
|
|
|
def stashInPersist(s: String): Unit = {
|
|
|
|
|
actor ! Cmd(s)
|
|
|
|
|
expectMsgPF() {
|
|
|
|
|
case ex: IllegalStateException if ex.getMessage.startsWith("Do not call stash") ⇒ ()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stashInPersist("a")
|
|
|
|
|
stashInPersist("b")
|
|
|
|
|
stashInPersist("c")
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-09-15 09:24:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class SteppingInMemPersistentActorStashingSpec extends PersistenceSpec(
|
|
|
|
|
SteppingInmemJournal.config("persistence-stash").withFallback(PersistenceSpec.config("stepping-inmem", "SteppingInMemPersistentActorStashingSpec")))
|
|
|
|
|
with ImplicitSender {
|
|
|
|
|
import PersistentActorStashingSpec._
|
|
|
|
|
|
2016-06-02 14:06:57 +02:00
|
|
|
def stash[T <: NamedPersistentActor: ClassTag](): Unit = {
|
2015-09-15 09:24:46 +02:00
|
|
|
"handle async callback not happening until next message has been stashed" in {
|
2016-03-18 12:11:43 +08:00
|
|
|
val persistentActor = namedPersistentActor[T]
|
2015-09-15 09:24:46 +02:00
|
|
|
awaitAssert(SteppingInmemJournal.getRef("persistence-stash"), 3.seconds)
|
|
|
|
|
val journal = SteppingInmemJournal.getRef("persistence-stash")
|
|
|
|
|
|
|
|
|
|
// initial read highest
|
|
|
|
|
SteppingInmemJournal.step(journal)
|
|
|
|
|
|
|
|
|
|
persistentActor ! Cmd("a")
|
|
|
|
|
persistentActor ! Cmd("b")
|
|
|
|
|
|
|
|
|
|
// allow the write to complete, after the stash
|
|
|
|
|
SteppingInmemJournal.step(journal)
|
|
|
|
|
|
|
|
|
|
persistentActor ! Cmd("c")
|
|
|
|
|
// writing of c and b
|
|
|
|
|
SteppingInmemJournal.step(journal)
|
|
|
|
|
SteppingInmemJournal.step(journal)
|
|
|
|
|
|
2015-09-23 11:58:38 +02:00
|
|
|
within(3.seconds) {
|
|
|
|
|
awaitAssert {
|
|
|
|
|
persistentActor ! GetState
|
|
|
|
|
expectMsg(List("a", "c", "b"))
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-09-15 09:24:46 +02:00
|
|
|
}
|
2016-03-18 12:11:43 +08:00
|
|
|
}
|
2015-09-15 09:24:46 +02:00
|
|
|
|
2016-03-18 12:11:43 +08:00
|
|
|
"Stashing in a persistent actor mixed with persistAsync" must {
|
|
|
|
|
behave like stash[AsyncStashingPersistentActor]()
|
|
|
|
|
}
|
2016-06-02 14:06:57 +02:00
|
|
|
|
2016-03-18 12:11:43 +08:00
|
|
|
"Stashing(unstashAll called in handler) in a persistent actor mixed with persistAsync" must {
|
|
|
|
|
behave like stash[AsyncStashingWithinHandlerPersistentActor]()
|
2015-09-15 09:24:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class LeveldbPersistentActorStashingSpec extends PersistentActorStashingSpec(PersistenceSpec.config("leveldb", "LeveldbPersistentActorStashingSpec"))
|
2017-01-04 17:37:10 +01:00
|
|
|
class InmemPersistentActorStashingSpec extends PersistentActorStashingSpec(PersistenceSpec.config("inmem", "InmemPersistentActorStashingSpec"))
|