2018-10-29 17:19:37 +08:00
/*
2019-01-02 18:55:26 +08:00
* Copyright ( C ) 2015 - 2019 Lightbend Inc . < https : //www.lightbend.com>
2015-11-06 16:10:46 +01:00
*/
2018-03-13 23:45:55 +09:00
2015-11-06 16:10:46 +01:00
package akka.persistence.journal
2015-11-17 16:50:54 +02:00
import java.net.URISyntaxException
2015-11-06 16:10:46 +01:00
import java.util.concurrent.TimeoutException
2015-11-17 16:50:54 +02:00
import akka.actor._
import akka.persistence. { AtomicWrite , DeleteMessagesFailure , DeleteSnapshotFailure , DeleteSnapshotsFailure , JournalProtocol , NonPersistentRepr , Persistence , SaveSnapshotFailure , SnapshotProtocol }
import akka.util.Helpers.Requiring
2015-11-06 16:10:46 +01:00
import com.typesafe.config.Config
2015-11-17 16:50:54 +02:00
import scala.concurrent.duration._
object PersistencePluginProxy {
2015-11-06 16:10:46 +01:00
final case class TargetLocation ( address : Address )
private case object InitTimeout
def setTargetLocation ( system : ActorSystem , address : Address ) : Unit = {
Persistence ( system ) . journalFor ( null ) ! TargetLocation ( address )
if ( system . settings . config . getString ( "akka.persistence.snapshot-store.plugin" ) != "" )
Persistence ( system ) . snapshotStoreFor ( null ) ! TargetLocation ( address )
}
2015-11-17 16:50:54 +02:00
def start ( system : ActorSystem ) : Unit = {
Persistence ( system ) . journalFor ( null )
if ( system . settings . config . getString ( "akka.persistence.snapshot-store.plugin" ) != "" )
Persistence ( system ) . snapshotStoreFor ( null )
}
2015-11-06 16:10:46 +01:00
private sealed trait PluginType {
def qualifier : String
}
private case object Journal extends PluginType {
override def qualifier : String = "journal"
}
private case object SnapshotStore extends PluginType {
override def qualifier : String = "snapshot-store"
}
}
2015-11-17 16:50:54 +02:00
/* *
* PersistencePluginProxyExtensionImpl is an `Extension` that enables initialization of the `PersistencePluginProxy`
* via configuration , without requiring any code changes or the creation of any actors .
* @param system The actor system to initialize the extension for
*/
class PersistencePluginProxyExtensionImpl ( system : ActorSystem ) extends Extension {
PersistencePluginProxy . start ( system )
}
object PersistencePluginProxyExtension extends ExtensionId [ PersistencePluginProxyExtensionImpl ] with ExtensionIdProvider {
override def createExtension ( system : ExtendedActorSystem ) : PersistencePluginProxyExtensionImpl = new PersistencePluginProxyExtensionImpl ( system )
override def lookup ( ) : ExtensionId [ _ <: Extension ] = PersistencePluginProxyExtension
override def get ( system : ActorSystem ) : PersistencePluginProxyExtensionImpl = super . get ( system )
}
final class PersistencePluginProxy ( config : Config ) extends Actor with Stash with ActorLogging {
import PersistencePluginProxy._
2015-11-06 16:10:46 +01:00
import JournalProtocol._
import SnapshotProtocol._
private val pluginId = self . path . name
private val pluginType : PluginType = pluginId match {
2019-02-09 15:25:39 +01:00
case "akka.persistence.journal.proxy" => Journal
case "akka.persistence.snapshot-store.proxy" => SnapshotStore
case other =>
2015-11-06 16:10:46 +01:00
throw new IllegalArgumentException ( "Unknown plugin type: " + other )
}
2015-11-17 16:50:54 +02:00
private val initTimeout : FiniteDuration = config . getDuration ( "init-timeout" , MILLISECONDS ) . millis
2015-11-06 16:10:46 +01:00
private val targetPluginId : String = {
val key = s" target- ${ pluginType . qualifier } -plugin "
config . getString ( key ) . requiring ( _ != "" , s" $pluginId . $key must be defined " )
}
private val startTarget : Boolean = config . getBoolean ( s" start-target- ${ pluginType . qualifier } " )
override def preStart ( ) : Unit = {
if ( startTarget ) {
val target = pluginType match {
2019-02-09 15:25:39 +01:00
case Journal =>
2015-11-06 16:10:46 +01:00
log . info ( "Starting target journal [{}]" , targetPluginId )
Persistence ( context . system ) . journalFor ( targetPluginId )
2019-02-09 15:25:39 +01:00
case SnapshotStore =>
2015-11-06 16:10:46 +01:00
log . info ( "Starting target snapshot-store [{}]" , targetPluginId )
Persistence ( context . system ) . snapshotStoreFor ( targetPluginId )
}
context . become ( active ( target , targetAtThisNode = true ) )
} else {
2015-11-17 16:50:54 +02:00
val targetAddressKey = s" target- ${ pluginType . qualifier } -address "
val targetAddress = config . getString ( targetAddressKey )
if ( targetAddress != "" ) {
try {
log . info ( "Setting target {} address to {}" , pluginType . qualifier , targetAddress )
PersistencePluginProxy . setTargetLocation ( context . system , AddressFromURIString ( targetAddress ) )
} catch {
2019-02-09 15:25:39 +01:00
case _ : URISyntaxException => log . warning ( "Invalid URL provided for target {} address: {}" , pluginType . qualifier , targetAddress )
2015-11-17 16:50:54 +02:00
}
}
context . system . scheduler . scheduleOnce ( initTimeout , self , InitTimeout ) ( context . dispatcher )
2015-11-06 16:10:46 +01:00
}
}
private val selfAddress : Address =
context . system . asInstanceOf [ ExtendedActorSystem ] . provider . getDefaultAddress
private def timeoutException ( ) = new TimeoutException ( s" Target ${ pluginType . qualifier } not initialized. " +
2015-11-17 16:50:54 +02:00
s" Use `PersistencePluginProxy.setTargetLocation` or set `target- ${ pluginType . qualifier } -address` " )
2015-11-06 16:10:46 +01:00
def receive = init
def init : Receive = {
2019-02-09 15:25:39 +01:00
case TargetLocation ( address ) =>
2015-11-06 16:10:46 +01:00
context . setReceiveTimeout ( 1. second ) // for retries
context . become ( identifying ( address ) )
2019-02-09 15:25:39 +01:00
case InitTimeout =>
2015-11-17 16:50:54 +02:00
log . info ( "Initialization timed-out (after {}), Use `PersistencePluginProxy.setTargetLocation` or set `target-{}-address`" , initTimeout , pluginType . qualifier )
2015-11-06 16:10:46 +01:00
context . become ( initTimedOut )
unstashAll ( ) // will trigger appropriate failures
2019-02-09 15:25:39 +01:00
case Terminated ( _ ) =>
case msg =>
2015-11-06 16:10:46 +01:00
stash ( )
}
def becomeIdentifying ( address : Address ) : Unit = {
sendIdentify ( address )
context . setReceiveTimeout ( 1. second ) // for retries
context . become ( identifying ( address ) )
}
def sendIdentify ( address : Address ) : Unit = {
val sel = context . actorSelection ( RootActorPath ( address ) / "system" / targetPluginId )
log . info ( "Trying to identify target {} at {}" , pluginType . qualifier , sel )
sel ! Identify ( targetPluginId )
}
def identifying ( address : Address ) : Receive = ( {
2019-02-09 15:25:39 +01:00
case ActorIdentity ( `targetPluginId` , Some ( target ) ) =>
2015-11-06 16:10:46 +01:00
log . info ( "Found target {} at [{}]" , pluginType . qualifier , address )
context . setReceiveTimeout ( Duration . Undefined )
2015-11-17 16:50:54 +02:00
context . watch ( target )
2015-11-06 16:10:46 +01:00
unstashAll ( )
context . become ( active ( target , address == selfAddress ) )
2019-02-09 15:25:39 +01:00
case _ : ActorIdentity => // will retry after ReceiveTimeout
case Terminated ( _ ) =>
case ReceiveTimeout =>
2015-11-06 16:10:46 +01:00
sendIdentify ( address )
} : Receive ) . orElse ( init )
def active ( targetJournal : ActorRef , targetAtThisNode : Boolean ) : Receive = {
2019-02-09 15:25:39 +01:00
case TargetLocation ( address ) =>
2015-11-06 16:10:46 +01:00
if ( targetAtThisNode && address != selfAddress )
becomeIdentifying ( address )
2019-02-09 15:25:39 +01:00
case Terminated ( `targetJournal` ) =>
2015-11-17 16:50:54 +02:00
context . unwatch ( targetJournal )
context . become ( initTimedOut )
2019-02-09 15:25:39 +01:00
case Terminated ( _ ) =>
case InitTimeout =>
case msg =>
2015-11-17 16:50:54 +02:00
targetJournal forward msg
2015-11-06 16:10:46 +01:00
}
def initTimedOut : Receive = {
2019-02-09 15:25:39 +01:00
case req : JournalProtocol . Request => req match { // exhaustive match
case WriteMessages ( messages , persistentActor , actorInstanceId ) =>
2015-11-06 16:10:46 +01:00
persistentActor ! WriteMessagesFailed ( timeoutException )
messages . foreach {
2019-02-09 15:25:39 +01:00
case a : AtomicWrite =>
a . payload . foreach { p =>
2015-11-06 16:10:46 +01:00
persistentActor ! WriteMessageFailure ( p , timeoutException , actorInstanceId )
}
2019-02-09 15:25:39 +01:00
case r : NonPersistentRepr =>
2015-11-06 16:10:46 +01:00
persistentActor ! LoopMessageSuccess ( r . payload , actorInstanceId )
}
2019-02-09 15:25:39 +01:00
case ReplayMessages ( fromSequenceNr , toSequenceNr , max , persistenceId , persistentActor ) =>
2015-11-06 16:10:46 +01:00
persistentActor ! ReplayMessagesFailure ( timeoutException )
2019-02-09 15:25:39 +01:00
case DeleteMessagesTo ( persistenceId , toSequenceNr , persistentActor ) =>
2015-11-06 16:10:46 +01:00
persistentActor ! DeleteMessagesFailure ( timeoutException , toSequenceNr )
}
2019-02-09 15:25:39 +01:00
case req : SnapshotProtocol . Request => req match { // exhaustive match
case LoadSnapshot ( persistenceId , criteria , toSequenceNr ) =>
2016-11-17 10:39:18 +01:00
sender ( ) ! LoadSnapshotFailed ( timeoutException )
2019-02-09 15:25:39 +01:00
case SaveSnapshot ( metadata , snapshot ) =>
2015-11-06 16:10:46 +01:00
sender ( ) ! SaveSnapshotFailure ( metadata , timeoutException )
2019-02-09 15:25:39 +01:00
case DeleteSnapshot ( metadata ) =>
2015-11-06 16:10:46 +01:00
sender ( ) ! DeleteSnapshotFailure ( metadata , timeoutException )
2019-02-09 15:25:39 +01:00
case DeleteSnapshots ( persistenceId , criteria ) =>
2015-11-06 16:10:46 +01:00
sender ( ) ! DeleteSnapshotsFailure ( criteria , timeoutException )
}
2019-02-09 15:25:39 +01:00
case TargetLocation ( address ) =>
2015-11-06 16:10:46 +01:00
becomeIdentifying ( address )
2019-02-09 15:25:39 +01:00
case Terminated ( _ ) =>
2015-11-17 16:50:54 +02:00
2019-02-09 15:25:39 +01:00
case other =>
2015-11-06 16:10:46 +01:00
val e = timeoutException ( )
2015-11-17 16:50:54 +02:00
log . error ( e , "Failed PersistencePluginProxy request: {}" , e . getMessage )
2015-11-06 16:10:46 +01:00
}
}