2018-10-29 17:19:37 +08:00
|
|
|
|
/*
|
2020-01-02 07:24:59 -05:00
|
|
|
|
* Copyright (C) 2016-2020 Lightbend Inc. <https://www.lightbend.com>
|
2016-02-05 18:19:24 +01:00
|
|
|
|
*/
|
2018-03-13 23:45:55 +09:00
|
|
|
|
|
2016-02-05 18:19:24 +01:00
|
|
|
|
package akka.persistence
|
|
|
|
|
|
|
|
|
|
|
|
import scala.concurrent.duration._
|
2020-04-27 20:32:18 +08:00
|
|
|
|
|
2016-02-05 18:19:24 +01:00
|
|
|
|
import com.typesafe.config.ConfigFactory
|
2020-04-27 20:32:18 +08:00
|
|
|
|
|
|
|
|
|
|
import akka.actor._
|
2016-02-05 18:19:24 +01:00
|
|
|
|
import akka.persistence.JournalProtocol._
|
2020-04-27 20:32:18 +08:00
|
|
|
|
import akka.testkit._
|
2016-02-05 18:19:24 +01:00
|
|
|
|
|
|
|
|
|
|
object PersistentActorJournalProtocolSpec {
|
|
|
|
|
|
|
|
|
|
|
|
val config = ConfigFactory.parseString("""
|
|
|
|
|
|
puppet {
|
|
|
|
|
|
class = "akka.persistence.JournalPuppet"
|
|
|
|
|
|
max-message-batch-size = 10
|
|
|
|
|
|
}
|
|
|
|
|
|
akka.persistence.journal.plugin = puppet
|
|
|
|
|
|
akka.persistence.snapshot-store.plugin = "akka.persistence.no-snapshot-store"
|
|
|
|
|
|
""")
|
|
|
|
|
|
|
|
|
|
|
|
sealed trait Command
|
|
|
|
|
|
case class Persist(id: Int, msgs: Any*) extends Command
|
|
|
|
|
|
case class PersistAsync(id: Int, msgs: Any*) extends Command
|
|
|
|
|
|
case class Multi(cmd: Command*) extends Command
|
|
|
|
|
|
case class Echo(id: Int) extends Command
|
|
|
|
|
|
case class Fail(ex: Throwable) extends Command
|
|
|
|
|
|
case class Done(id: Int, sub: Int)
|
|
|
|
|
|
|
|
|
|
|
|
case class PreStart(name: String)
|
|
|
|
|
|
case class PreRestart(name: String)
|
|
|
|
|
|
case class PostRestart(name: String)
|
|
|
|
|
|
case class PostStop(name: String)
|
|
|
|
|
|
|
|
|
|
|
|
class A(monitor: ActorRef) extends PersistentActor {
|
|
|
|
|
|
|
|
|
|
|
|
def persistenceId = self.path.name
|
|
|
|
|
|
|
|
|
|
|
|
override def preStart(): Unit = monitor ! PreStart(persistenceId)
|
|
|
|
|
|
override def preRestart(reason: Throwable, msg: Option[Any]): Unit = monitor ! PreRestart(persistenceId)
|
|
|
|
|
|
override def postRestart(reason: Throwable): Unit = monitor ! PostRestart(persistenceId)
|
|
|
|
|
|
override def postStop(): Unit = monitor ! PostStop(persistenceId)
|
|
|
|
|
|
|
|
|
|
|
|
def receiveRecover = {
|
2019-02-09 15:25:39 +01:00
|
|
|
|
case x => monitor ! x
|
2016-02-05 18:19:24 +01:00
|
|
|
|
}
|
2019-03-11 10:38:24 +01:00
|
|
|
|
def receiveCommand = behavior.orElse {
|
2019-02-09 15:25:39 +01:00
|
|
|
|
case m: Multi => m.cmd.foreach(behavior)
|
2016-02-05 18:19:24 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
val behavior: Receive = {
|
2019-02-09 15:25:39 +01:00
|
|
|
|
case p: Persist => P(p)
|
|
|
|
|
|
case p: PersistAsync => PA(p)
|
|
|
|
|
|
case Echo(id) => sender() ! Done(id, 0)
|
|
|
|
|
|
case Fail(ex) => throw ex
|
2016-02-05 18:19:24 +01:00
|
|
|
|
}
|
2019-02-09 15:25:39 +01:00
|
|
|
|
val doNothing = (_: Any) => ()
|
2016-02-05 18:19:24 +01:00
|
|
|
|
|
|
|
|
|
|
def P(p: Persist): Unit = {
|
|
|
|
|
|
var sub = 0
|
2019-02-09 15:25:39 +01:00
|
|
|
|
persistAll(p.msgs.toList) { e =>
|
2016-02-05 18:19:24 +01:00
|
|
|
|
sender() ! Done(p.id, { sub += 1; sub })
|
|
|
|
|
|
behavior.applyOrElse(e, doNothing)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
def PA(p: PersistAsync): Unit = {
|
|
|
|
|
|
var sub = 0
|
2019-02-09 15:25:39 +01:00
|
|
|
|
persistAllAsync(p.msgs.toList) { e =>
|
2016-02-05 18:19:24 +01:00
|
|
|
|
sender() ! Done(p.id, { sub += 1; sub })
|
|
|
|
|
|
behavior.applyOrElse(e, doNothing)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-02-17 17:07:15 +01:00
|
|
|
|
object JournalPuppet extends ExtensionId[JournalProbe] with ExtensionIdProvider {
|
2020-08-10 12:54:38 +02:00
|
|
|
|
override def lookup = JournalPuppet
|
2017-02-17 17:07:15 +01:00
|
|
|
|
|
|
|
|
|
|
override def createExtension(system: ExtendedActorSystem): JournalProbe =
|
|
|
|
|
|
new JournalProbe()(system)
|
|
|
|
|
|
|
|
|
|
|
|
override def get(system: ActorSystem): JournalProbe = super.get(system)
|
2019-12-05 16:01:23 +01:00
|
|
|
|
override def get(system: ClassicActorSystemProvider): JournalProbe = super.get(system)
|
2017-02-17 17:07:15 +01:00
|
|
|
|
}
|
2016-02-05 18:19:24 +01:00
|
|
|
|
class JournalProbe(implicit private val system: ExtendedActorSystem) extends Extension {
|
|
|
|
|
|
val probe = TestProbe()
|
|
|
|
|
|
val ref = probe.ref
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class JournalPuppet extends Actor {
|
|
|
|
|
|
val ref = JournalPuppet(context.system).ref
|
|
|
|
|
|
def receive = {
|
2019-03-11 10:38:24 +01:00
|
|
|
|
case x => ref.forward(x)
|
2016-02-05 18:19:24 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
import PersistentActorJournalProtocolSpec._
|
|
|
|
|
|
|
|
|
|
|
|
class PersistentActorJournalProtocolSpec extends AkkaSpec(config) with ImplicitSender {
|
|
|
|
|
|
|
|
|
|
|
|
val journal = JournalPuppet(system).probe
|
|
|
|
|
|
|
|
|
|
|
|
case class Msgs(msg: Any*)
|
|
|
|
|
|
|
|
|
|
|
|
def expectWrite(subject: ActorRef, msgs: Msgs*): WriteMessages = {
|
|
|
|
|
|
val w = journal.expectMsgType[WriteMessages]
|
|
|
|
|
|
withClue(s"$w: ") {
|
|
|
|
|
|
w.persistentActor should ===(subject)
|
|
|
|
|
|
w.messages.size should ===(msgs.size)
|
|
|
|
|
|
w.messages.zip(msgs).foreach {
|
2019-02-09 15:25:39 +01:00
|
|
|
|
case (AtomicWrite(writes), msg) =>
|
2016-02-05 18:19:24 +01:00
|
|
|
|
writes.size should ===(msg.msg.size)
|
|
|
|
|
|
writes.zip(msg.msg).foreach {
|
2019-02-09 15:25:39 +01:00
|
|
|
|
case (PersistentRepr(evt, _), m) =>
|
2016-02-05 18:19:24 +01:00
|
|
|
|
evt should ===(m)
|
|
|
|
|
|
}
|
2019-02-09 15:25:39 +01:00
|
|
|
|
case x => fail(s"unexpected $x")
|
2016-02-05 18:19:24 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
w
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
def confirm(w: WriteMessages): Unit = {
|
|
|
|
|
|
journal.send(w.persistentActor, WriteMessagesSuccessful)
|
|
|
|
|
|
w.messages.foreach {
|
2019-02-09 15:25:39 +01:00
|
|
|
|
case AtomicWrite(msgs) =>
|
2019-03-11 10:38:24 +01:00
|
|
|
|
msgs.foreach(msg => w.persistentActor.tell(WriteMessageSuccess(msg, w.actorInstanceId), msg.sender))
|
2019-02-09 15:25:39 +01:00
|
|
|
|
case NonPersistentRepr(msg, sender) => w.persistentActor.tell(msg, sender)
|
2016-02-05 18:19:24 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
def startActor(name: String): ActorRef = {
|
|
|
|
|
|
val subject = system.actorOf(Props(new A(testActor)), name)
|
|
|
|
|
|
subject ! Echo(0)
|
|
|
|
|
|
expectMsg(PreStart(name))
|
|
|
|
|
|
journal.expectMsgType[ReplayMessages]
|
|
|
|
|
|
journal.reply(RecoverySuccess(0L))
|
|
|
|
|
|
expectMsg(RecoveryCompleted)
|
|
|
|
|
|
expectMsg(Done(0, 0))
|
|
|
|
|
|
subject
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
"A PersistentActor’s journal protocol" must {
|
|
|
|
|
|
|
|
|
|
|
|
"not send WriteMessages while a write is still outstanding" when {
|
|
|
|
|
|
|
|
|
|
|
|
"using simple persist()" in {
|
|
|
|
|
|
val subject = startActor("test-1")
|
|
|
|
|
|
subject ! Persist(1, "a-1")
|
|
|
|
|
|
val w1 = expectWrite(subject, Msgs("a-1"))
|
|
|
|
|
|
subject ! Persist(2, "a-2")
|
2019-04-05 14:43:23 +02:00
|
|
|
|
expectNoMessage(300.millis)
|
2016-02-05 18:19:24 +01:00
|
|
|
|
journal.msgAvailable should ===(false)
|
|
|
|
|
|
confirm(w1)
|
|
|
|
|
|
expectMsg(Done(1, 1))
|
|
|
|
|
|
val w2 = expectWrite(subject, Msgs("a-2"))
|
|
|
|
|
|
confirm(w2)
|
|
|
|
|
|
expectMsg(Done(2, 1))
|
|
|
|
|
|
subject ! PoisonPill
|
|
|
|
|
|
expectMsg(PostStop("test-1"))
|
|
|
|
|
|
journal.msgAvailable should ===(false)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
"using nested persist()" in {
|
|
|
|
|
|
val subject = startActor("test-2")
|
|
|
|
|
|
subject ! Persist(1, Persist(2, "a-1"))
|
|
|
|
|
|
val w1 = expectWrite(subject, Msgs(Persist(2, "a-1")))
|
|
|
|
|
|
subject ! Persist(3, "a-2")
|
2019-04-05 14:43:23 +02:00
|
|
|
|
expectNoMessage(300.millis)
|
2016-02-05 18:19:24 +01:00
|
|
|
|
journal.msgAvailable should ===(false)
|
|
|
|
|
|
confirm(w1)
|
|
|
|
|
|
expectMsg(Done(1, 1))
|
|
|
|
|
|
val w2 = expectWrite(subject, Msgs("a-1"))
|
|
|
|
|
|
confirm(w2)
|
|
|
|
|
|
expectMsg(Done(2, 1))
|
|
|
|
|
|
val w3 = expectWrite(subject, Msgs("a-2"))
|
|
|
|
|
|
confirm(w3)
|
|
|
|
|
|
expectMsg(Done(3, 1))
|
|
|
|
|
|
subject ! PoisonPill
|
|
|
|
|
|
expectMsg(PostStop("test-2"))
|
|
|
|
|
|
journal.msgAvailable should ===(false)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
"using nested multiple persist()" in {
|
|
|
|
|
|
val subject = startActor("test-3")
|
|
|
|
|
|
subject ! Multi(Persist(1, Persist(2, "a-1")), Persist(3, "a-2"))
|
|
|
|
|
|
val w1 = expectWrite(subject, Msgs(Persist(2, "a-1")), Msgs("a-2"))
|
|
|
|
|
|
confirm(w1)
|
|
|
|
|
|
expectMsg(Done(1, 1))
|
|
|
|
|
|
expectMsg(Done(3, 1))
|
|
|
|
|
|
val w2 = expectWrite(subject, Msgs("a-1"))
|
|
|
|
|
|
confirm(w2)
|
|
|
|
|
|
expectMsg(Done(2, 1))
|
|
|
|
|
|
subject ! PoisonPill
|
|
|
|
|
|
expectMsg(PostStop("test-3"))
|
|
|
|
|
|
journal.msgAvailable should ===(false)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
"using large number of persist() calls" in {
|
|
|
|
|
|
val subject = startActor("test-4")
|
2019-02-09 15:25:39 +01:00
|
|
|
|
subject ! Multi(Vector.tabulate(30)(i => Persist(i, s"a-$i")): _*)
|
|
|
|
|
|
val w1 = expectWrite(subject, Vector.tabulate(30)(i => Msgs(s"a-$i")): _*)
|
2016-02-05 18:19:24 +01:00
|
|
|
|
confirm(w1)
|
2019-02-09 15:25:39 +01:00
|
|
|
|
for (i <- 0 until 30) expectMsg(Done(i, 1))
|
2016-02-05 18:19:24 +01:00
|
|
|
|
subject ! PoisonPill
|
|
|
|
|
|
expectMsg(PostStop("test-4"))
|
|
|
|
|
|
journal.msgAvailable should ===(false)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
"using large number of persistAsync() calls" in {
|
2019-02-09 15:25:39 +01:00
|
|
|
|
def msgs(start: Int, end: Int) = (start until end).map(i => Msgs(s"a-$i-1", s"a-$i-2"))
|
|
|
|
|
|
def commands(start: Int, end: Int) = (start until end).map(i => PersistAsync(i, s"a-$i-1", s"a-$i-2"))
|
|
|
|
|
|
def expectDone(start: Int, end: Int) = for (i <- start until end; j <- 1 to 2) expectMsg(Done(i, j))
|
2016-02-05 18:19:24 +01:00
|
|
|
|
|
|
|
|
|
|
val subject = startActor("test-5")
|
|
|
|
|
|
subject ! PersistAsync(-1, "a" +: commands(20, 30): _*)
|
|
|
|
|
|
subject ! Multi(commands(0, 10): _*)
|
|
|
|
|
|
subject ! Multi(commands(10, 20): _*)
|
|
|
|
|
|
val w0 = expectWrite(subject, Msgs("a" +: commands(20, 30): _*))
|
2019-04-05 14:43:23 +02:00
|
|
|
|
journal.expectNoMessage(300.millis)
|
2016-02-05 18:19:24 +01:00
|
|
|
|
confirm(w0)
|
2019-03-11 10:38:24 +01:00
|
|
|
|
(1 to 11).foreach(x => expectMsg(Done(-1, x)))
|
2016-02-05 18:19:24 +01:00
|
|
|
|
val w1 = expectWrite(subject, msgs(0, 20): _*)
|
2019-04-05 14:43:23 +02:00
|
|
|
|
journal.expectNoMessage(300.millis)
|
2016-02-05 18:19:24 +01:00
|
|
|
|
confirm(w1)
|
|
|
|
|
|
expectDone(0, 20)
|
|
|
|
|
|
val w2 = expectWrite(subject, msgs(20, 30): _*)
|
|
|
|
|
|
confirm(w2)
|
|
|
|
|
|
expectDone(20, 30)
|
|
|
|
|
|
subject ! PoisonPill
|
|
|
|
|
|
expectMsg(PostStop("test-5"))
|
|
|
|
|
|
journal.msgAvailable should ===(false)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
"the actor fails with queued events" in {
|
|
|
|
|
|
val subject = startActor("test-6")
|
|
|
|
|
|
subject ! PersistAsync(1, "a-1")
|
|
|
|
|
|
val w1 = expectWrite(subject, Msgs("a-1"))
|
|
|
|
|
|
subject ! PersistAsync(2, "a-2")
|
2019-03-11 10:38:24 +01:00
|
|
|
|
EventFilter[Exception](message = "K-BOOM!", occurrences = 1).intercept {
|
2016-02-05 18:19:24 +01:00
|
|
|
|
subject ! Fail(new Exception("K-BOOM!"))
|
|
|
|
|
|
expectMsg(PreRestart("test-6"))
|
|
|
|
|
|
expectMsg(PostRestart("test-6"))
|
|
|
|
|
|
journal.expectMsgType[ReplayMessages]
|
|
|
|
|
|
}
|
|
|
|
|
|
journal.reply(RecoverySuccess(0L))
|
|
|
|
|
|
expectMsg(RecoveryCompleted)
|
|
|
|
|
|
confirm(w1)
|
|
|
|
|
|
subject ! PoisonPill
|
|
|
|
|
|
expectMsg(PostStop("test-6"))
|
|
|
|
|
|
journal.msgAvailable should ===(false)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|