=per #15942 Support resume after recovery failure

* also improved fault handling in various places (bugs found)

* and manually triggered Update must be distinguished from scheduled
  auto updates, otherwise manual Update will schedule extra auto updates
This commit is contained in:
Patrik Nordwall 2014-12-14 21:45:22 +01:00
parent 72d54626f3
commit 9b5a446a4a
8 changed files with 239 additions and 95 deletions

View file

@ -50,6 +50,13 @@ object Update {
Update(await, replayMax)
}
/**
* INTERNAL API
*/
private[akka] object PersistentView {
private final case class ScheduledUpdate(replayMax: Long)
}
/**
* A view replicates the persistent message stream of a [[PersistentActor]]. Implementation classes receive
* the message stream directly from the Journal. These messages can be processed to update internal state
@ -74,6 +81,7 @@ object Update {
* - [[autoUpdateReplayMax]] for limiting the number of replayed messages per view update cycle
*/
trait PersistentView extends Actor with Snapshotter with Stash with StashFactory {
import PersistentView._
import JournalProtocol._
import SnapshotProtocol.LoadSnapshotResult
import context.dispatcher
@ -175,6 +183,8 @@ trait PersistentView extends Actor with Snapshotter with Stash with StashFactory
override def preStart(): Unit = {
super.preStart()
self ! Recover(replayMax = autoUpdateReplayMax)
if (autoUpdate) schedule = Some(context.system.scheduler.schedule(autoUpdateInterval, autoUpdateInterval,
self, ScheduledUpdate(autoUpdateReplayMax)))
}
/**
@ -282,7 +292,8 @@ trait PersistentView extends Actor with Snapshotter with Stash with StashFactory
private var stashUpdate = await
override def stateReceive(receive: Receive, message: Any) = message match {
case Update(false, _) // ignore
case ScheduledUpdate(_) // ignore
case Update(false, _) // ignore
case u @ Update(true, _) if !stashUpdate
stashUpdate = true
internalStash.stash()
@ -299,7 +310,6 @@ trait PersistentView extends Actor with Snapshotter with Stash with StashFactory
onReplayComplete(await)
case ReplayMessagesFailure(cause)
onReplayComplete(await)
// FIXME what happens if RecoveryFailure is handled, i.e. actor is not stopped?
PersistentView.super.aroundReceive(receive, RecoveryFailure(cause)(None))
case other
internalStash.stash()
@ -310,8 +320,6 @@ trait PersistentView extends Actor with Snapshotter with Stash with StashFactory
*/
private def onReplayComplete(await: Boolean): Unit = {
changeState(idle)
if (autoUpdate) schedule = Some(context.system.scheduler.scheduleOnce(autoUpdateInterval, self,
Update(await = false, autoUpdateReplayMax)))
if (await) internalStash.unstashAll()
}
}
@ -339,7 +347,9 @@ trait PersistentView extends Actor with Snapshotter with Stash with StashFactory
}
def replayCompleted(receive: Receive): Unit = {
// FIXME what happens if RecoveryFailure is handled, i.e. actor is not stopped?
// in case the actor resumes the state must be `idle`
changeState(idle)
PersistentView.super.aroundReceive(receive, RecoveryFailure(cause)(Some((failed.sequenceNr, failed.payload))))
}
}
@ -354,11 +364,15 @@ trait PersistentView extends Actor with Snapshotter with Stash with StashFactory
override def recoveryRunning: Boolean = false
override def stateReceive(receive: Receive, message: Any): Unit = message match {
case r: Recover // ignore
case Update(awaitUpdate, replayMax)
changeState(replayStarted(await = awaitUpdate))
journal ! ReplayMessages(lastSequenceNr + 1L, Long.MaxValue, replayMax, persistenceId, self)
case other PersistentView.super.aroundReceive(receive, other)
case r: Recover // ignore
case ScheduledUpdate(replayMax) changeStateToReplayStarted(await = false, replayMax)
case Update(awaitUpdate, replayMax) changeStateToReplayStarted(awaitUpdate, replayMax)
case other PersistentView.super.aroundReceive(receive, other)
}
def changeStateToReplayStarted(await: Boolean, replayMax: Long): Unit = {
changeState(replayStarted(await))
journal ! ReplayMessages(lastSequenceNr + 1L, Long.MaxValue, replayMax, persistenceId, self)
}
}