Skip to content
Permalink
31f654768f
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
180 lines (157 sloc) 6.01 KB
/*
* Copyright (C) 2009-2021 Lightbend Inc. <https://www.lightbend.com>
*/
package akka.persistence.snapshot
import scala.concurrent.ExecutionContext
import scala.concurrent.Future
import scala.concurrent.duration._
import akka.actor._
import akka.pattern.CircuitBreaker
import akka.pattern.pipe
import akka.persistence._
/**
* Abstract snapshot store.
*/
trait SnapshotStore extends Actor with ActorLogging {
import SnapshotProtocol._
private val extension = Persistence(context.system)
private val publish = extension.settings.internal.publishPluginCommands
private val breaker = {
val cfg = extension.configFor(self)
val maxFailures = cfg.getInt("circuit-breaker.max-failures")
val callTimeout = cfg.getDuration("circuit-breaker.call-timeout", MILLISECONDS).millis
val resetTimeout = cfg.getDuration("circuit-breaker.reset-timeout", MILLISECONDS).millis
CircuitBreaker(context.system.scheduler, maxFailures, callTimeout, resetTimeout)
}
final def receive = receiveSnapshotStore.orElse[Any, Unit](receivePluginInternal)
final val receiveSnapshotStore: Actor.Receive = {
val eventStream = context.system.eventStream // used from Future callbacks
implicit val ec: ExecutionContext = context.dispatcher
{
case LoadSnapshot(persistenceId, criteria, toSequenceNr) =>
if (criteria == SnapshotSelectionCriteria.None) {
senderPersistentActor() ! LoadSnapshotResult(snapshot = None, toSequenceNr)
} else {
breaker
.withCircuitBreaker(loadAsync(persistenceId, criteria.limit(toSequenceNr)))
.map { sso =>
LoadSnapshotResult(sso, toSequenceNr)
}
.recover {
case e => LoadSnapshotFailed(e)
}
.pipeTo(senderPersistentActor())
}
case SaveSnapshot(metadata, snapshot) =>
val md = metadata.copy(timestamp = System.currentTimeMillis)
breaker
.withCircuitBreaker(saveAsync(md, snapshot))
.map { _ =>
SaveSnapshotSuccess(md)
}
.recover {
case e => SaveSnapshotFailure(metadata, e)
}
.to(self, senderPersistentActor())
case evt: SaveSnapshotSuccess =>
try tryReceivePluginInternal(evt)
finally senderPersistentActor() ! evt // sender is persistentActor
case evt @ SaveSnapshotFailure(metadata, _) =>
try {
tryReceivePluginInternal(evt)
breaker.withCircuitBreaker(deleteAsync(metadata))
} finally senderPersistentActor() ! evt // sender is persistentActor
case d @ DeleteSnapshot(metadata) =>
breaker
.withCircuitBreaker(deleteAsync(metadata))
.map {
case _ => DeleteSnapshotSuccess(metadata)
}
.recover {
case e => DeleteSnapshotFailure(metadata, e)
}
.pipeTo(self)(senderPersistentActor())
.onComplete {
case _ => if (publish) eventStream.publish(d)
}
case evt: DeleteSnapshotSuccess =>
try tryReceivePluginInternal(evt)
finally senderPersistentActor() ! evt
case evt: DeleteSnapshotFailure =>
try tryReceivePluginInternal(evt)
finally senderPersistentActor() ! evt
case d @ DeleteSnapshots(persistenceId, criteria) =>
breaker
.withCircuitBreaker(deleteAsync(persistenceId, criteria))
.map {
case _ => DeleteSnapshotsSuccess(criteria)
}
.recover {
case e => DeleteSnapshotsFailure(criteria, e)
}
.pipeTo(self)(senderPersistentActor())
.onComplete {
case _ => if (publish) eventStream.publish(d)
}
case evt: DeleteSnapshotsFailure =>
try tryReceivePluginInternal(evt)
finally senderPersistentActor() ! evt // sender is persistentActor
case evt: DeleteSnapshotsSuccess =>
try tryReceivePluginInternal(evt)
finally senderPersistentActor() ! evt
}
}
/** Documents intent that the sender() is expected to be the PersistentActor */
@inline private final def senderPersistentActor(): ActorRef = sender()
private def tryReceivePluginInternal(evt: Any): Unit =
if (receivePluginInternal.isDefinedAt(evt)) receivePluginInternal(evt)
//#snapshot-store-plugin-api
/**
* Plugin API: asynchronously loads a snapshot.
*
* If the future `Option` is `None` then all events will be replayed,
* i.e. there was no snapshot. If snapshot could not be loaded the `Future`
* should be completed with failure. That is important because events may
* have been deleted and just replaying the events might not result in a valid
* state.
*
* This call is protected with a circuit-breaker.
*
* @param persistenceId id of the persistent actor.
* @param criteria selection criteria for loading.
*/
def loadAsync(persistenceId: String, criteria: SnapshotSelectionCriteria): Future[Option[SelectedSnapshot]]
/**
* Plugin API: asynchronously saves a snapshot.
*
* This call is protected with a circuit-breaker.
*
* @param metadata snapshot metadata.
* @param snapshot snapshot.
*/
def saveAsync(metadata: SnapshotMetadata, snapshot: Any): Future[Unit]
/**
* Plugin API: deletes the snapshot identified by `metadata`.
*
* This call is protected with a circuit-breaker.
*
* @param metadata snapshot metadata.
*/
def deleteAsync(metadata: SnapshotMetadata): Future[Unit]
/**
* Plugin API: deletes all snapshots matching `criteria`.
*
* This call is protected with a circuit-breaker.
*
* @param persistenceId id of the persistent actor.
* @param criteria selection criteria for deleting.
*/
def deleteAsync(persistenceId: String, criteria: SnapshotSelectionCriteria): Future[Unit]
/**
* Plugin API
* Allows plugin implementers to use `f pipeTo self` and
* handle additional messages for implementing advanced features
*/
def receivePluginInternal: Actor.Receive = Actor.emptyBehavior
//#snapshot-store-plugin-api
}