=per #15916 Read highestSeqNr first in replay
* we need to read the higestSeqNr anyway and it is better to do it first and limit the asyncReadHighestSequenceNr to that (instead of Long.MaxValue) * return the highestSeqNr in the ReplayMessagesSuccess * this also removes one become state in PersistentActor recovery logic
This commit is contained in:
parent
aaa6b623e1
commit
09a2f9c248
12 changed files with 103 additions and 126 deletions
|
|
@ -92,50 +92,54 @@ abstract class JournalSpec(config: Config) extends PluginSpec(config) {
|
||||||
"replay all messages" in {
|
"replay all messages" in {
|
||||||
journal ! ReplayMessages(1, Long.MaxValue, Long.MaxValue, pid, receiverProbe.ref)
|
journal ! ReplayMessages(1, Long.MaxValue, Long.MaxValue, pid, receiverProbe.ref)
|
||||||
1 to 5 foreach { i ⇒ receiverProbe.expectMsg(replayedMessage(i)) }
|
1 to 5 foreach { i ⇒ receiverProbe.expectMsg(replayedMessage(i)) }
|
||||||
receiverProbe.expectMsg(ReplayMessagesSuccess)
|
receiverProbe.expectMsg(ReplayMessagesSuccess(highestSequenceNr = 5L))
|
||||||
}
|
}
|
||||||
"replay messages using a lower sequence number bound" in {
|
"replay messages using a lower sequence number bound" in {
|
||||||
journal ! ReplayMessages(3, Long.MaxValue, Long.MaxValue, pid, receiverProbe.ref)
|
journal ! ReplayMessages(3, Long.MaxValue, Long.MaxValue, pid, receiverProbe.ref)
|
||||||
3 to 5 foreach { i ⇒ receiverProbe.expectMsg(replayedMessage(i)) }
|
3 to 5 foreach { i ⇒ receiverProbe.expectMsg(replayedMessage(i)) }
|
||||||
receiverProbe.expectMsg(ReplayMessagesSuccess)
|
receiverProbe.expectMsg(ReplayMessagesSuccess(highestSequenceNr = 5L))
|
||||||
}
|
}
|
||||||
"replay messages using an upper sequence number bound" in {
|
"replay messages using an upper sequence number bound" in {
|
||||||
journal ! ReplayMessages(1, 3, Long.MaxValue, pid, receiverProbe.ref)
|
journal ! ReplayMessages(1, 3, Long.MaxValue, pid, receiverProbe.ref)
|
||||||
1 to 3 foreach { i ⇒ receiverProbe.expectMsg(replayedMessage(i)) }
|
1 to 3 foreach { i ⇒ receiverProbe.expectMsg(replayedMessage(i)) }
|
||||||
receiverProbe.expectMsg(ReplayMessagesSuccess)
|
receiverProbe.expectMsg(ReplayMessagesSuccess(highestSequenceNr = 5L))
|
||||||
}
|
}
|
||||||
"replay messages using a count limit" in {
|
"replay messages using a count limit" in {
|
||||||
journal ! ReplayMessages(1, Long.MaxValue, 3, pid, receiverProbe.ref)
|
journal ! ReplayMessages(1, Long.MaxValue, 3, pid, receiverProbe.ref)
|
||||||
1 to 3 foreach { i ⇒ receiverProbe.expectMsg(replayedMessage(i)) }
|
1 to 3 foreach { i ⇒ receiverProbe.expectMsg(replayedMessage(i)) }
|
||||||
receiverProbe.expectMsg(ReplayMessagesSuccess)
|
receiverProbe.expectMsg(ReplayMessagesSuccess(highestSequenceNr = 5L))
|
||||||
}
|
}
|
||||||
"replay messages using a lower and upper sequence number bound" in {
|
"replay messages using a lower and upper sequence number bound" in {
|
||||||
journal ! ReplayMessages(2, 4, Long.MaxValue, pid, receiverProbe.ref)
|
journal ! ReplayMessages(2, 4, Long.MaxValue, pid, receiverProbe.ref)
|
||||||
2 to 4 foreach { i ⇒ receiverProbe.expectMsg(replayedMessage(i)) }
|
2 to 4 foreach { i ⇒ receiverProbe.expectMsg(replayedMessage(i)) }
|
||||||
receiverProbe.expectMsg(ReplayMessagesSuccess)
|
receiverProbe.expectMsg(ReplayMessagesSuccess(highestSequenceNr = 5L))
|
||||||
}
|
}
|
||||||
"replay messages using a lower and upper sequence number bound and a count limit" in {
|
"replay messages using a lower and upper sequence number bound and a count limit" in {
|
||||||
journal ! ReplayMessages(2, 4, 2, pid, receiverProbe.ref)
|
journal ! ReplayMessages(2, 4, 2, pid, receiverProbe.ref)
|
||||||
2 to 3 foreach { i ⇒ receiverProbe.expectMsg(replayedMessage(i)) }
|
2 to 3 foreach { i ⇒ receiverProbe.expectMsg(replayedMessage(i)) }
|
||||||
receiverProbe.expectMsg(ReplayMessagesSuccess)
|
receiverProbe.expectMsg(ReplayMessagesSuccess(highestSequenceNr = 5L))
|
||||||
}
|
}
|
||||||
"replay a single if lower sequence number bound equals upper sequence number bound" in {
|
"replay a single if lower sequence number bound equals upper sequence number bound" in {
|
||||||
journal ! ReplayMessages(2, 2, Long.MaxValue, pid, receiverProbe.ref)
|
journal ! ReplayMessages(2, 2, Long.MaxValue, pid, receiverProbe.ref)
|
||||||
2 to 2 foreach { i ⇒ receiverProbe.expectMsg(replayedMessage(i)) }
|
2 to 2 foreach { i ⇒ receiverProbe.expectMsg(replayedMessage(i)) }
|
||||||
receiverProbe.expectMsg(ReplayMessagesSuccess)
|
receiverProbe.expectMsg(ReplayMessagesSuccess(highestSequenceNr = 5L))
|
||||||
}
|
}
|
||||||
"replay a single message if count limit equals 1" in {
|
"replay a single message if count limit equals 1" in {
|
||||||
journal ! ReplayMessages(2, 4, 1, pid, receiverProbe.ref)
|
journal ! ReplayMessages(2, 4, 1, pid, receiverProbe.ref)
|
||||||
2 to 2 foreach { i ⇒ receiverProbe.expectMsg(replayedMessage(i)) }
|
2 to 2 foreach { i ⇒ receiverProbe.expectMsg(replayedMessage(i)) }
|
||||||
receiverProbe.expectMsg(ReplayMessagesSuccess)
|
receiverProbe.expectMsg(ReplayMessagesSuccess(highestSequenceNr = 5L))
|
||||||
}
|
}
|
||||||
"not replay messages if count limit equals 0" in {
|
"not replay messages if count limit equals 0" in {
|
||||||
journal ! ReplayMessages(2, 4, 0, pid, receiverProbe.ref)
|
journal ! ReplayMessages(2, 4, 0, pid, receiverProbe.ref)
|
||||||
receiverProbe.expectMsg(ReplayMessagesSuccess)
|
receiverProbe.expectMsg(ReplayMessagesSuccess(highestSequenceNr = 5L))
|
||||||
}
|
}
|
||||||
"not replay messages if lower sequence number bound is greater than upper sequence number bound" in {
|
"not replay messages if lower sequence number bound is greater than upper sequence number bound" in {
|
||||||
journal ! ReplayMessages(3, 2, Long.MaxValue, pid, receiverProbe.ref)
|
journal ! ReplayMessages(3, 2, Long.MaxValue, pid, receiverProbe.ref)
|
||||||
receiverProbe.expectMsg(ReplayMessagesSuccess)
|
receiverProbe.expectMsg(ReplayMessagesSuccess(highestSequenceNr = 5L))
|
||||||
|
}
|
||||||
|
"not replay messages if the persistent actor has not yet written messages" in {
|
||||||
|
journal ! ReplayMessages(0, Long.MaxValue, Long.MaxValue, "non-existing-pid", receiverProbe.ref)
|
||||||
|
receiverProbe.expectMsg(ReplayMessagesSuccess(highestSequenceNr = 0L))
|
||||||
}
|
}
|
||||||
"not replay permanently deleted messages (range deletion)" in {
|
"not replay permanently deleted messages (range deletion)" in {
|
||||||
val receiverProbe2 = TestProbe()
|
val receiverProbe2 = TestProbe()
|
||||||
|
|
@ -152,18 +156,6 @@ abstract class JournalSpec(config: Config) extends PluginSpec(config) {
|
||||||
receiverProbe2.expectNoMsg(200.millis)
|
receiverProbe2.expectNoMsg(200.millis)
|
||||||
}
|
}
|
||||||
|
|
||||||
"return a highest stored sequence number > 0 if the persistent actor has already written messages and the message log is non-empty" in {
|
|
||||||
journal ! ReadHighestSequenceNr(3L, pid, receiverProbe.ref)
|
|
||||||
receiverProbe.expectMsg(ReadHighestSequenceNrSuccess(5))
|
|
||||||
|
|
||||||
journal ! ReadHighestSequenceNr(5L, pid, receiverProbe.ref)
|
|
||||||
receiverProbe.expectMsg(ReadHighestSequenceNrSuccess(5))
|
|
||||||
}
|
|
||||||
"return a highest stored sequence number == 0 if the persistent actor has not yet written messages" in {
|
|
||||||
journal ! ReadHighestSequenceNr(0L, "non-existing-pid", receiverProbe.ref)
|
|
||||||
receiverProbe.expectMsg(ReadHighestSequenceNrSuccess(0))
|
|
||||||
}
|
|
||||||
|
|
||||||
"reject non-serializable events" in {
|
"reject non-serializable events" in {
|
||||||
// there is no chance that a journal could create a data representation for type of event
|
// there is no chance that a journal could create a data representation for type of event
|
||||||
val notSerializableEvent = new Object { override def toString = "not serializable" }
|
val notSerializableEvent = new Object { override def toString = "not serializable" }
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,10 @@ interface AsyncRecoveryPlugin {
|
||||||
* marked as deleted. In this case a replayed message's `deleted` method must
|
* marked as deleted. In this case a replayed message's `deleted` method must
|
||||||
* return `true`.
|
* return `true`.
|
||||||
*
|
*
|
||||||
|
* The `toSequenceNr` is the lowest of what was returned by
|
||||||
|
* {@link #doAsyncReadHighestSequenceNr} and what the user specified as
|
||||||
|
* recovery {@link akka.persistence.Recovery} parameter.
|
||||||
|
*
|
||||||
* @param persistenceId
|
* @param persistenceId
|
||||||
* id of the persistent actor.
|
* id of the persistent actor.
|
||||||
* @param fromSequenceNr
|
* @param fromSequenceNr
|
||||||
|
|
@ -34,12 +38,16 @@ interface AsyncRecoveryPlugin {
|
||||||
* @param replayCallback
|
* @param replayCallback
|
||||||
* called to replay a single message. Can be called from any thread.
|
* called to replay a single message. Can be called from any thread.
|
||||||
*/
|
*/
|
||||||
Future<Void> doAsyncReplayMessages(String persistenceId, long fromSequenceNr,
|
Future<Void> doAsyncReplayMessages(String persistenceId, long fromSequenceNr,
|
||||||
long toSequenceNr, long max, Consumer<PersistentRepr> replayCallback);
|
long toSequenceNr, long max, Consumer<PersistentRepr> replayCallback);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Java API, Plugin API: asynchronously reads the highest stored sequence
|
* Java API, Plugin API: asynchronously reads the highest stored sequence
|
||||||
* number for the given `persistenceId`.
|
* number for the given `persistenceId`. The persistent actor will use the
|
||||||
|
* highest sequence number after recovery as the starting point when
|
||||||
|
* persisting new events. This sequence number is also used as `toSequenceNr`
|
||||||
|
* in subsequent call to [[#asyncReplayMessages]] unless the user has
|
||||||
|
* specified a lower `toSequenceNr`.
|
||||||
*
|
*
|
||||||
* @param persistenceId
|
* @param persistenceId
|
||||||
* id of the persistent actor.
|
* id of the persistent actor.
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,6 @@
|
||||||
*/
|
*/
|
||||||
package akka.persistence
|
package akka.persistence
|
||||||
|
|
||||||
import akka.persistence.JournalProtocol.ReplayMessagesSuccess
|
|
||||||
|
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
import scala.collection.breakOut
|
import scala.collection.breakOut
|
||||||
import scala.collection.immutable
|
import scala.collection.immutable
|
||||||
|
|
|
||||||
|
|
@ -215,8 +215,8 @@ private[persistence] trait Eventsourced extends Snapshotter with Stash with Stas
|
||||||
|
|
||||||
override def unhandled(message: Any): Unit = {
|
override def unhandled(message: Any): Unit = {
|
||||||
message match {
|
message match {
|
||||||
case RecoveryCompleted | ReadHighestSequenceNrSuccess | ReadHighestSequenceNrFailure ⇒ // mute
|
case RecoveryCompleted ⇒ // mute
|
||||||
case m ⇒ super.unhandled(m)
|
case m ⇒ super.unhandled(m)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -466,8 +466,8 @@ private[persistence] trait Eventsourced extends Snapshotter with Stash with Stas
|
||||||
* Processes replayed messages, if any. The actor's `receiveRecover` is invoked with the replayed
|
* Processes replayed messages, if any. The actor's `receiveRecover` is invoked with the replayed
|
||||||
* events.
|
* events.
|
||||||
*
|
*
|
||||||
* If replay succeeds it switches to `initializing` state and requests the highest stored sequence
|
* If replay succeeds it got highest stored sequence number response from the journal and then switches
|
||||||
* number from the journal. Otherwise the actor is stopped.
|
* to `processingCommands` state. Otherwise the actor is stopped.
|
||||||
* If replay succeeds the `onReplaySuccess` callback method is called, otherwise `onReplayFailure`.
|
* If replay succeeds the `onReplaySuccess` callback method is called, otherwise `onReplayFailure`.
|
||||||
*
|
*
|
||||||
* All incoming messages are stashed.
|
* All incoming messages are stashed.
|
||||||
|
|
@ -485,37 +485,15 @@ private[persistence] trait Eventsourced extends Snapshotter with Stash with Stas
|
||||||
case NonFatal(t) ⇒
|
case NonFatal(t) ⇒
|
||||||
try onReplayFailure(t, Some(p.payload)) finally context.stop(self)
|
try onReplayFailure(t, Some(p.payload)) finally context.stop(self)
|
||||||
}
|
}
|
||||||
case ReplayMessagesSuccess ⇒
|
case ReplayMessagesSuccess(highestSeqNr) ⇒
|
||||||
onReplaySuccess() // callback for subclass implementation
|
onReplaySuccess() // callback for subclass implementation
|
||||||
changeState(initializing(recoveryBehavior))
|
|
||||||
journal ! ReadHighestSequenceNr(lastSequenceNr, persistenceId, self)
|
|
||||||
case ReplayMessagesFailure(cause) ⇒
|
|
||||||
try onReplayFailure(cause, event = None) finally context.stop(self)
|
|
||||||
case other ⇒
|
|
||||||
internalStash.stash()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Processes the highest stored sequence number response from the journal and then switches
|
|
||||||
* to `processingCommands` state.
|
|
||||||
* All incoming messages are stashed.
|
|
||||||
*/
|
|
||||||
private def initializing(recoveryBehavior: Receive) = new State {
|
|
||||||
override def toString: String = "initializing"
|
|
||||||
override def recoveryRunning: Boolean = true
|
|
||||||
|
|
||||||
override def stateReceive(receive: Receive, message: Any) = message match {
|
|
||||||
case ReadHighestSequenceNrSuccess(highest) ⇒
|
|
||||||
changeState(processingCommands)
|
changeState(processingCommands)
|
||||||
sequenceNr = highest
|
sequenceNr = highestSeqNr
|
||||||
setLastSequenceNr(highest)
|
setLastSequenceNr(highestSeqNr)
|
||||||
internalStash.unstashAll()
|
internalStash.unstashAll()
|
||||||
Eventsourced.super.aroundReceive(recoveryBehavior, RecoveryCompleted)
|
Eventsourced.super.aroundReceive(recoveryBehavior, RecoveryCompleted)
|
||||||
case ReadHighestSequenceNrFailure(cause) ⇒
|
case ReplayMessagesFailure(cause) ⇒
|
||||||
log.error(cause, "PersistentActor could not retrieve highest sequence number and must " +
|
try onReplayFailure(cause, event = None) finally context.stop(self)
|
||||||
"therefore be stopped. (persisten id = [{}]).", persistenceId)
|
|
||||||
context.stop(self)
|
|
||||||
case other ⇒
|
case other ⇒
|
||||||
internalStash.stash()
|
internalStash.stash()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -122,8 +122,13 @@ private[persistence] object JournalProtocol {
|
||||||
/**
|
/**
|
||||||
* Reply message to a successful [[ReplayMessages]] request. This reply is sent to the requestor
|
* Reply message to a successful [[ReplayMessages]] request. This reply is sent to the requestor
|
||||||
* after all [[ReplayedMessage]] have been sent (if any).
|
* after all [[ReplayedMessage]] have been sent (if any).
|
||||||
|
*
|
||||||
|
* It includes the highest stored sequence number of a given persistent actor. Note that the
|
||||||
|
* replay might have been limited to a lower sequence number.
|
||||||
|
*
|
||||||
|
* @param highestSequenceNr highest stored sequence number.
|
||||||
*/
|
*/
|
||||||
case object ReplayMessagesSuccess
|
case class ReplayMessagesSuccess(highestSequenceNr: Long)
|
||||||
extends Response with DeadLetterSuppression
|
extends Response with DeadLetterSuppression
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -133,29 +138,4 @@ private[persistence] object JournalProtocol {
|
||||||
final case class ReplayMessagesFailure(cause: Throwable)
|
final case class ReplayMessagesFailure(cause: Throwable)
|
||||||
extends Response with DeadLetterSuppression
|
extends Response with DeadLetterSuppression
|
||||||
|
|
||||||
/**
|
|
||||||
* Request to read the highest stored sequence number of a given persistent actor.
|
|
||||||
*
|
|
||||||
* @param fromSequenceNr optional hint where to start searching for the maximum sequence number.
|
|
||||||
* @param persistenceId requesting persistent actor id.
|
|
||||||
* @param persistentActor requesting persistent actor.
|
|
||||||
*/
|
|
||||||
final case class ReadHighestSequenceNr(fromSequenceNr: Long = 1L, persistenceId: String, persistentActor: ActorRef)
|
|
||||||
extends Request
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reply message to a successful [[ReadHighestSequenceNr]] request.
|
|
||||||
*
|
|
||||||
* @param highestSequenceNr read highest sequence number.
|
|
||||||
*/
|
|
||||||
final case class ReadHighestSequenceNrSuccess(highestSequenceNr: Long)
|
|
||||||
extends Response
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reply message to a failed [[ReadHighestSequenceNr]] request.
|
|
||||||
*
|
|
||||||
* @param cause failure cause.
|
|
||||||
*/
|
|
||||||
final case class ReadHighestSequenceNrFailure(cause: Throwable)
|
|
||||||
extends Response
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -293,7 +293,7 @@ trait PersistentView extends Actor with Snapshotter with Stash with StashFactory
|
||||||
case NonFatal(t) ⇒
|
case NonFatal(t) ⇒
|
||||||
changeState(ignoreRemainingReplay(t))
|
changeState(ignoreRemainingReplay(t))
|
||||||
}
|
}
|
||||||
case ReplayMessagesSuccess ⇒
|
case _: ReplayMessagesSuccess ⇒
|
||||||
onReplayComplete()
|
onReplayComplete()
|
||||||
case ReplayMessagesFailure(cause) ⇒
|
case ReplayMessagesFailure(cause) ⇒
|
||||||
try onReplayError(cause) finally onReplayComplete()
|
try onReplayError(cause) finally onReplayComplete()
|
||||||
|
|
@ -339,8 +339,8 @@ trait PersistentView extends Actor with Snapshotter with Stash with StashFactory
|
||||||
// replay must be a full replay (up to the highest stored sequence number)
|
// replay must be a full replay (up to the highest stored sequence number)
|
||||||
// Recover(lastSequenceNr) is sent by preRestart
|
// Recover(lastSequenceNr) is sent by preRestart
|
||||||
setLastSequenceNr(Long.MaxValue)
|
setLastSequenceNr(Long.MaxValue)
|
||||||
case ReplayMessagesSuccess ⇒ replayCompleted(receive)
|
case _: ReplayMessagesSuccess ⇒ replayCompleted(receive)
|
||||||
case _ ⇒ internalStash.stash()
|
case _ ⇒ internalStash.stash()
|
||||||
}
|
}
|
||||||
|
|
||||||
def replayCompleted(receive: Receive): Unit = {
|
def replayCompleted(receive: Receive): Unit = {
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,9 @@ trait AsyncRecovery {
|
||||||
* as deleted. In this case a replayed message's `deleted` method must return
|
* as deleted. In this case a replayed message's `deleted` method must return
|
||||||
* `true`.
|
* `true`.
|
||||||
*
|
*
|
||||||
|
* The `toSequenceNr` is the lowest of what was returned by [[#asyncReadHighestSequenceNr]]
|
||||||
|
* and what the user specified as recovery [[akka.persistence.Recovery]] parameter.
|
||||||
|
*
|
||||||
* @param persistenceId persistent actor id.
|
* @param persistenceId persistent actor id.
|
||||||
* @param fromSequenceNr sequence number where replay should start (inclusive).
|
* @param fromSequenceNr sequence number where replay should start (inclusive).
|
||||||
* @param toSequenceNr sequence number where replay should end (inclusive).
|
* @param toSequenceNr sequence number where replay should end (inclusive).
|
||||||
|
|
@ -38,7 +41,10 @@ trait AsyncRecovery {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plugin API: asynchronously reads the highest stored sequence number for the
|
* Plugin API: asynchronously reads the highest stored sequence number for the
|
||||||
* given `persistenceId`.
|
* given `persistenceId`. The persistent actor will use the highest sequence
|
||||||
|
* number after recovery as the starting point when persisting new events.
|
||||||
|
* This sequence number is also used as `toSequenceNr` in subsequent call
|
||||||
|
* to [[#asyncReplayMessages]] unless the user has specified a lower `toSequenceNr`.
|
||||||
*
|
*
|
||||||
* @param persistenceId persistent actor id.
|
* @param persistenceId persistent actor id.
|
||||||
* @param fromSequenceNr hint where to start searching for the highest sequence
|
* @param fromSequenceNr hint where to start searching for the highest sequence
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import scala.util.control.NonFatal
|
||||||
import scala.util.Try
|
import scala.util.Try
|
||||||
import scala.util.Success
|
import scala.util.Success
|
||||||
import scala.util.Failure
|
import scala.util.Failure
|
||||||
|
import akka.AkkaException
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract journal, optimized for asynchronous, non-blocking writes.
|
* Abstract journal, optimized for asynchronous, non-blocking writes.
|
||||||
|
|
@ -93,30 +94,29 @@ trait AsyncWriteJournal extends Actor with WriteJournalBase with AsyncRecovery {
|
||||||
}
|
}
|
||||||
|
|
||||||
case r @ ReplayMessages(fromSequenceNr, toSequenceNr, max, persistenceId, persistentActor) ⇒
|
case r @ ReplayMessages(fromSequenceNr, toSequenceNr, max, persistenceId, persistentActor) ⇒
|
||||||
// Send replayed messages and replay result to persistentActor directly. No need
|
|
||||||
// to resequence replayed messages relative to written and looped messages.
|
asyncReadHighestSequenceNr(persistenceId, fromSequenceNr).flatMap { highSeqNr ⇒
|
||||||
asyncReplayMessages(persistenceId, fromSequenceNr, toSequenceNr, max) { p ⇒
|
val toSeqNr = math.min(toSequenceNr, highSeqNr)
|
||||||
if (!p.deleted) // old records from 2.3 may still have the deleted flag
|
if (highSeqNr == 0L || fromSequenceNr > toSeqNr)
|
||||||
adaptFromJournal(p).foreach { adaptedPersistentRepr ⇒
|
Future.successful(highSeqNr)
|
||||||
persistentActor.tell(ReplayedMessage(adaptedPersistentRepr), Actor.noSender)
|
else {
|
||||||
}
|
// Send replayed messages and replay result to persistentActor directly. No need
|
||||||
} map {
|
// to resequence replayed messages relative to written and looped messages.
|
||||||
case _ ⇒ ReplayMessagesSuccess
|
asyncReplayMessages(persistenceId, fromSequenceNr, toSeqNr, max) { p ⇒
|
||||||
} recover {
|
if (!p.deleted) // old records from 2.3 may still have the deleted flag
|
||||||
|
adaptFromJournal(p).foreach { adaptedPersistentRepr ⇒
|
||||||
|
persistentActor.tell(ReplayedMessage(adaptedPersistentRepr), Actor.noSender)
|
||||||
|
}
|
||||||
|
}.map(_ ⇒ highSeqNr)
|
||||||
|
}
|
||||||
|
}.map {
|
||||||
|
highSeqNr ⇒ ReplayMessagesSuccess(highSeqNr)
|
||||||
|
}.recover {
|
||||||
case e ⇒ ReplayMessagesFailure(e)
|
case e ⇒ ReplayMessagesFailure(e)
|
||||||
} pipeTo persistentActor onSuccess {
|
}.pipeTo(persistentActor).onSuccess {
|
||||||
case _ if publish ⇒ context.system.eventStream.publish(r)
|
case _ if publish ⇒ context.system.eventStream.publish(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
case ReadHighestSequenceNr(fromSequenceNr, persistenceId, persistentActor) ⇒
|
|
||||||
// Send read highest sequence number to persistentActor directly. No need
|
|
||||||
// to resequence the result relative to written and looped messages.
|
|
||||||
asyncReadHighestSequenceNr(persistenceId, fromSequenceNr).map {
|
|
||||||
highest ⇒ ReadHighestSequenceNrSuccess(highest)
|
|
||||||
} recover {
|
|
||||||
case e ⇒ ReadHighestSequenceNrFailure(e)
|
|
||||||
} pipeTo persistentActor
|
|
||||||
|
|
||||||
case d @ DeleteMessagesTo(persistenceId, toSequenceNr, persistentActor) ⇒
|
case d @ DeleteMessagesTo(persistenceId, toSequenceNr, persistentActor) ⇒
|
||||||
asyncDeleteMessagesTo(persistenceId, toSequenceNr) onComplete {
|
asyncDeleteMessagesTo(persistenceId, toSequenceNr) onComplete {
|
||||||
case Success(_) ⇒ if (publish) context.system.eventStream.publish(d)
|
case Success(_) ⇒ if (publish) context.system.eventStream.publish(d)
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import scala.language.postfixOps
|
||||||
private[persistence] trait AsyncWriteProxy extends AsyncWriteJournal with Stash with ActorLogging {
|
private[persistence] trait AsyncWriteProxy extends AsyncWriteJournal with Stash with ActorLogging {
|
||||||
import AsyncWriteProxy._
|
import AsyncWriteProxy._
|
||||||
import AsyncWriteTarget._
|
import AsyncWriteTarget._
|
||||||
|
import context.dispatcher
|
||||||
|
|
||||||
private var isInitialized = false
|
private var isInitialized = false
|
||||||
private var store: ActorRef = _
|
private var store: ActorRef = _
|
||||||
|
|
@ -54,7 +55,9 @@ private[persistence] trait AsyncWriteProxy extends AsyncWriteJournal with Stash
|
||||||
}
|
}
|
||||||
|
|
||||||
def asyncReadHighestSequenceNr(persistenceId: String, fromSequenceNr: Long): Future[Long] =
|
def asyncReadHighestSequenceNr(persistenceId: String, fromSequenceNr: Long): Future[Long] =
|
||||||
(store ? ReadHighestSequenceNr(persistenceId, fromSequenceNr)).mapTo[Long]
|
(store ? ReplayMessages(persistenceId, fromSequenceNr = 0L, toSequenceNr = 0L, max = 0L)).map {
|
||||||
|
case ReplaySuccess(highest) ⇒ highest
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -78,13 +81,11 @@ private[persistence] object AsyncWriteTarget {
|
||||||
final case class ReplayMessages(persistenceId: String, fromSequenceNr: Long, toSequenceNr: Long, max: Long)
|
final case class ReplayMessages(persistenceId: String, fromSequenceNr: Long, toSequenceNr: Long, max: Long)
|
||||||
|
|
||||||
@SerialVersionUID(1L)
|
@SerialVersionUID(1L)
|
||||||
case object ReplaySuccess
|
case class ReplaySuccess(highestSequenceNr: Long)
|
||||||
|
|
||||||
@SerialVersionUID(1L)
|
@SerialVersionUID(1L)
|
||||||
final case class ReplayFailure(cause: Throwable)
|
final case class ReplayFailure(cause: Throwable)
|
||||||
|
|
||||||
@SerialVersionUID(1L)
|
|
||||||
final case class ReadHighestSequenceNr(persistenceId: String, fromSequenceNr: Long)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -100,7 +101,7 @@ private class ReplayMediator(replayCallback: PersistentRepr ⇒ Unit, replayComp
|
||||||
|
|
||||||
def receive = {
|
def receive = {
|
||||||
case p: PersistentRepr ⇒ replayCallback(p)
|
case p: PersistentRepr ⇒ replayCallback(p)
|
||||||
case ReplaySuccess ⇒
|
case _: ReplaySuccess ⇒
|
||||||
replayCompletionPromise.success(())
|
replayCompletionPromise.success(())
|
||||||
context.stop(self)
|
context.stop(self)
|
||||||
case ReplayFailure(cause) ⇒
|
case ReplayFailure(cause) ⇒
|
||||||
|
|
|
||||||
|
|
@ -85,9 +85,9 @@ private[persistence] class InmemStore extends Actor with InmemMessages with Writ
|
||||||
case DeleteMessagesTo(pid, tsnr) ⇒
|
case DeleteMessagesTo(pid, tsnr) ⇒
|
||||||
sender() ! (1L to tsnr foreach { snr ⇒ delete(pid, snr) })
|
sender() ! (1L to tsnr foreach { snr ⇒ delete(pid, snr) })
|
||||||
case ReplayMessages(pid, fromSnr, toSnr, max) ⇒
|
case ReplayMessages(pid, fromSnr, toSnr, max) ⇒
|
||||||
read(pid, fromSnr, toSnr, max).foreach { sender() ! _ }
|
val highest = highestSequenceNr(pid)
|
||||||
sender() ! ReplaySuccess
|
if (highest != 0L && max != 0L)
|
||||||
case ReadHighestSequenceNr(persistenceId, _) ⇒
|
read(pid, fromSnr, math.min(toSnr, highest), max).foreach { sender() ! _ }
|
||||||
sender() ! highestSequenceNr(persistenceId)
|
sender() ! ReplaySuccess(highest)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ class SharedLeveldbStore extends { val configPath = "akka.persistence.journal.le
|
||||||
|
|
||||||
def receive = {
|
def receive = {
|
||||||
case WriteMessages(messages) ⇒
|
case WriteMessages(messages) ⇒
|
||||||
|
// TODO it would be nice to DRY this with AsyncWriteJournal, but this is using
|
||||||
|
// AsyncWriteProxy message protocol
|
||||||
val prepared = Try(preparePersistentBatch(messages))
|
val prepared = Try(preparePersistentBatch(messages))
|
||||||
val writeResult = (prepared match {
|
val writeResult = (prepared match {
|
||||||
case Success(prep) ⇒
|
case Success(prep) ⇒
|
||||||
|
|
@ -43,13 +45,24 @@ class SharedLeveldbStore extends { val configPath = "akka.persistence.journal.le
|
||||||
case DeleteMessagesTo(pid, tsnr) ⇒
|
case DeleteMessagesTo(pid, tsnr) ⇒
|
||||||
asyncDeleteMessagesTo(pid, tsnr).pipeTo(sender())
|
asyncDeleteMessagesTo(pid, tsnr).pipeTo(sender())
|
||||||
|
|
||||||
case ReadHighestSequenceNr(pid, fromSequenceNr) ⇒
|
case ReplayMessages(persistenceId, fromSequenceNr, toSequenceNr, max) ⇒
|
||||||
asyncReadHighestSequenceNr(pid, fromSequenceNr).pipeTo(sender())
|
// TODO it would be nice to DRY this with AsyncWriteJournal, but this is using
|
||||||
|
// AsyncWriteProxy message protocol
|
||||||
case ReplayMessages(pid, fromSnr, toSnr, max) ⇒
|
val replyTo = sender()
|
||||||
Try(replayMessages(numericId(pid), fromSnr, toSnr, max)(p ⇒ adaptFromJournal(p).foreach { sender() ! _ })) match {
|
asyncReadHighestSequenceNr(persistenceId, fromSequenceNr).flatMap { highSeqNr ⇒
|
||||||
case Success(max) ⇒ sender() ! ReplaySuccess
|
if (highSeqNr == 0L || max == 0L)
|
||||||
case Failure(cause) ⇒ sender() ! ReplayFailure(cause)
|
Future.successful(highSeqNr)
|
||||||
}
|
else {
|
||||||
|
val toSeqNr = math.min(toSequenceNr, highSeqNr)
|
||||||
|
asyncReplayMessages(persistenceId, fromSequenceNr, toSeqNr, max) { p ⇒
|
||||||
|
if (!p.deleted) // old records from 2.3 may still have the deleted flag
|
||||||
|
adaptFromJournal(p).foreach(replyTo ! _)
|
||||||
|
}.map(_ ⇒ highSeqNr)
|
||||||
|
}
|
||||||
|
}.map {
|
||||||
|
highSeqNr ⇒ ReplaySuccess(highSeqNr)
|
||||||
|
}.recover {
|
||||||
|
case e ⇒ ReplayFailure(e)
|
||||||
|
}.pipeTo(replyTo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,14 +44,15 @@ object PersistentActorFailureSpec {
|
||||||
case w: WriteMessages if checkSerializable(w).exists(_.isFailure) ⇒
|
case w: WriteMessages if checkSerializable(w).exists(_.isFailure) ⇒
|
||||||
sender() ! checkSerializable(w)
|
sender() ! checkSerializable(w)
|
||||||
case ReplayMessages(pid, fromSnr, toSnr, max) ⇒
|
case ReplayMessages(pid, fromSnr, toSnr, max) ⇒
|
||||||
|
val highest = highestSequenceNr(pid)
|
||||||
val readFromStore = read(pid, fromSnr, toSnr, max)
|
val readFromStore = read(pid, fromSnr, toSnr, max)
|
||||||
if (readFromStore.length == 0)
|
if (readFromStore.length == 0)
|
||||||
sender() ! ReplaySuccess
|
sender() ! ReplaySuccess(highest)
|
||||||
else if (isCorrupt(readFromStore))
|
else if (isCorrupt(readFromStore))
|
||||||
sender() ! ReplayFailure(new SimulatedException(s"blahonga $fromSnr $toSnr"))
|
sender() ! ReplayFailure(new SimulatedException(s"blahonga $fromSnr $toSnr"))
|
||||||
else {
|
else {
|
||||||
readFromStore.foreach(sender() ! _)
|
readFromStore.foreach(sender() ! _)
|
||||||
sender() ! ReplaySuccess
|
sender() ! ReplaySuccess(highest)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue