2013-01-09 12:21:31 +01:00
/* *
* Copyright ( C ) 2009 - 2013 Typesafe Inc . < http : //www.typesafe.com>
*/
2012-09-12 11:18:42 +02:00
package akka.remote
2012-12-20 12:59:48 +01:00
import akka. { OnlyCauseStackTrace , AkkaException }
2012-09-12 11:18:42 +02:00
import akka.actor._
2013-03-05 16:19:54 +01:00
import akka.dispatch.sysmsg.SystemMessage
2012-09-12 11:18:42 +02:00
import akka.event.LoggingAdapter
import akka.pattern.pipe
import akka.remote.EndpointManager.Send
import akka.remote.RemoteProtocol.MessageProtocol
import akka.remote.transport.AkkaPduCodec._
import akka.remote.transport.AssociationHandle._
import akka.remote.transport. { AkkaPduCodec , Transport , AssociationHandle }
2013-03-26 11:25:09 +01:00
import akka.serialization.Serialization
2012-09-12 11:18:42 +02:00
import akka.util.ByteString
2013-03-26 11:25:09 +01:00
import scala.util.control.NonFatal
2012-12-20 12:54:43 +01:00
import akka.remote.transport.Transport.InvalidAssociationException
2013-03-20 20:38:49 +13:00
import java.io.NotSerializableException
2012-09-12 11:18:42 +02:00
2012-12-07 16:03:04 +01:00
/* *
2013-02-08 13:13:52 +01:00
* INTERNAL API
2012-12-07 16:03:04 +01:00
*/
private [ remote ] trait InboundMessageDispatcher {
2012-09-12 11:18:42 +02:00
def dispatch ( recipient : InternalActorRef ,
recipientAddress : Address ,
serializedMessage : MessageProtocol ,
senderOption : Option [ ActorRef ] ) : Unit
}
2013-02-08 13:13:52 +01:00
/* *
* INTERNAL API
*/
2012-12-07 16:03:04 +01:00
private [ remote ] class DefaultMessageDispatcher ( private val system : ExtendedActorSystem ,
private val provider : RemoteActorRefProvider ,
private val log : LoggingAdapter ) extends InboundMessageDispatcher {
2012-09-12 11:18:42 +02:00
private val remoteDaemon = provider . remoteDaemon
override def dispatch ( recipient : InternalActorRef ,
recipientAddress : Address ,
serializedMessage : MessageProtocol ,
senderOption : Option [ ActorRef ] ) : Unit = {
import provider.remoteSettings._
lazy val payload : AnyRef = MessageSerializer . deserialize ( system , serializedMessage )
2012-12-07 16:03:04 +01:00
def payloadClass : Class [ _ ] = if ( payload eq null ) null else payload . getClass
2012-09-12 11:18:42 +02:00
val sender : ActorRef = senderOption . getOrElse ( system . deadLetters )
val originalReceiver = recipient . path
2012-12-07 16:03:04 +01:00
def msgLog = s" RemoteMessage: [ $payload ] to [ $recipient ]<+[ $originalReceiver ] from [ $sender ] "
2012-09-12 11:18:42 +02:00
recipient match {
case `remoteDaemon` ⇒
2012-12-12 15:04:44 +01:00
if ( UntrustedMode ) log . debug ( "dropping daemon message in untrusted mode" )
else {
2012-11-23 10:15:19 +01:00
if ( LogReceive ) log . debug ( "received daemon message {}" , msgLog )
2013-03-26 18:17:50 +01:00
remoteDaemon ! payload
2012-09-12 11:18:42 +02:00
}
case l @ ( _ : LocalRef | _ : RepointableRef ) if l . isLocal ⇒
if ( LogReceive ) log . debug ( "received local message {}" , msgLog )
payload match {
case msg : PossiblyHarmful if UntrustedMode ⇒
log . debug ( "operating in UntrustedMode, dropping inbound PossiblyHarmful message of type {}" , msg . getClass )
case msg : SystemMessage ⇒ l . sendSystemMessage ( msg )
case msg ⇒ l . ! ( msg ) ( sender )
}
case r @ ( _ : RemoteRef | _ : RepointableRef ) if ! r . isLocal && ! UntrustedMode ⇒
if ( LogReceive ) log . debug ( "received remote-destined message {}" , msgLog )
if ( provider . transport . addresses ( recipientAddress ) )
// if it was originally addressed to us but is in fact remote from our point of view (i.e. remote-deployed)
r . ! ( payload ) ( sender )
else
log . error ( "dropping message {} for non-local recipient {} arriving at {} inbound addresses are {}" ,
payloadClass , r , recipientAddress , provider . transport . addresses )
case r ⇒ log . error ( "dropping message {} for unknown recipient {} arriving at {} inbound addresses are {}" ,
payloadClass , r , recipientAddress , provider . transport . addresses )
}
}
}
2012-12-07 16:03:04 +01:00
/* *
2013-02-08 13:13:52 +01:00
* INTERNAL API
2012-12-07 16:03:04 +01:00
*/
private [ remote ] object EndpointWriter {
/* *
* This message signals that the current association maintained by the local EndpointWriter and EndpointReader is
* to be overridden by a new inbound association . This is needed to avoid parallel inbound associations from the
2012-12-11 13:08:36 +01:00
* same remote endpoint : when a parallel inbound association is detected , the old one is removed and the new one is
* used instead .
* @param handle Handle of the new inbound association .
2012-12-07 16:03:04 +01:00
*/
2012-11-21 16:39:04 +01:00
case class TakeOver ( handle : AssociationHandle )
2012-09-12 11:18:42 +02:00
case object BackoffTimer
2013-02-25 12:11:39 +01:00
case object FlushAndStop
2012-09-12 11:18:42 +02:00
sealed trait State
case object Initializing extends State
case object Buffering extends State
case object Writing extends State
2013-01-14 10:04:49 +01:00
case object Handoff extends State
2012-09-12 11:18:42 +02:00
}
2013-02-08 13:13:52 +01:00
/* *
* INTERNAL API
*/
@SerialVersionUID ( 1L )
2012-12-20 12:59:48 +01:00
private [ remote ] class EndpointException ( msg : String , cause : Throwable ) extends AkkaException ( msg , cause ) with OnlyCauseStackTrace {
2012-12-18 13:46:10 +01:00
def this ( msg : String ) = this ( msg , null )
}
2013-02-08 13:13:52 +01:00
/* *
* INTERNAL API
*/
2013-03-07 18:08:07 +01:00
@SerialVersionUID ( 1L )
2012-12-07 16:03:04 +01:00
private [ remote ] case class InvalidAssociation ( localAddress : Address , remoteAddress : Address , cause : Throwable )
2012-09-12 11:18:42 +02:00
extends EndpointException ( "Invalid address: " + remoteAddress , cause )
2013-03-07 18:08:07 +01:00
/* *
* INTERNAL API
*/
@SerialVersionUID ( 1L )
private [ remote ] class EndpointDisassociatedException ( msg : String ) extends EndpointException ( msg )
/* *
* INTERNAL API
*/
@SerialVersionUID ( 1L )
private [ remote ] class EndpointAssociationException ( msg : String , cause : Throwable ) extends EndpointException ( msg , cause )
2013-02-08 13:13:52 +01:00
/* *
* INTERNAL API
*/
2013-03-20 20:38:49 +13:00
@SerialVersionUID ( 1L )
private [ remote ] class OversizedPayloadException ( msg : String ) extends EndpointException ( msg )
private [ remote ] abstract class EndpointActor (
2012-09-12 11:18:42 +02:00
val localAddress : Address ,
val remoteAddress : Address ,
val transport : Transport ,
2013-01-17 16:19:31 +01:00
val settings : RemoteSettings ,
2013-03-20 20:38:49 +13:00
val codec : AkkaPduCodec ) extends Actor with ActorLogging {
def inbound : Boolean
val eventPublisher = new EventPublisher ( context . system , log , settings . LogRemoteLifecycleEvents )
def publishError ( reason : Throwable ) : Unit = {
try
eventPublisher . notifyListeners ( AssociationErrorEvent ( reason , localAddress , remoteAddress , inbound ) )
catch { case NonFatal ( e ) ⇒ log . error ( e , "Unable to publish error event to EventStream." ) }
}
}
/* *
* INTERNAL API
*/
private [ remote ] class EndpointWriter (
handleOrActive : Option [ AssociationHandle ] ,
localAddress : Address ,
remoteAddress : Address ,
transport : Transport ,
settings : RemoteSettings ,
codec : AkkaPduCodec ) extends EndpointActor ( localAddress , remoteAddress , transport , settings , codec ) with Stash with FSM [ EndpointWriter . State , Unit ] {
2012-09-12 11:18:42 +02:00
import EndpointWriter._
import context.dispatcher
val extendedSystem : ExtendedActorSystem = context . system . asInstanceOf [ ExtendedActorSystem ]
2012-11-21 16:39:04 +01:00
2012-12-07 16:03:04 +01:00
var reader : Option [ ActorRef ] = None
var handle : Option [ AssociationHandle ] = handleOrActive // FIXME: refactor into state data
val readerId = Iterator from 0
2012-09-12 11:18:42 +02:00
2012-11-22 13:33:48 +01:00
override val supervisorStrategy = OneForOneStrategy ( ) { case NonFatal ( e ) ⇒ publishAndThrow ( e ) }
2012-09-12 11:18:42 +02:00
2012-12-21 18:11:56 +01:00
val msgDispatch = new DefaultMessageDispatcher ( extendedSystem , RARP ( extendedSystem ) . provider , log )
2012-09-12 11:18:42 +02:00
2013-01-14 10:04:49 +01:00
var inbound = handle . isDefined
2012-09-12 11:18:42 +02:00
2012-12-14 13:45:55 +01:00
private def publishAndThrow ( reason : Throwable ) : Nothing = {
2013-03-20 20:38:49 +13:00
publishError ( reason )
2012-12-14 13:45:55 +01:00
throw reason
}
2012-09-12 11:18:42 +02:00
2013-03-20 20:38:49 +13:00
private def publishAndStay ( reason : Throwable ) : State = {
publishError ( reason )
stay ( )
}
2012-09-12 11:18:42 +02:00
override def postRestart ( reason : Throwable ) : Unit = {
2012-12-07 16:03:04 +01:00
handle = None // Wipe out the possibly injected handle
2013-01-14 10:04:49 +01:00
inbound = false
2012-09-12 11:18:42 +02:00
preStart ( )
}
2012-12-21 18:11:56 +01:00
override def preStart ( ) : Unit =
startWith (
handle match {
case Some ( h ) ⇒
reader = startReadEndpoint ( h )
Writing
case None ⇒
transport . associate ( remoteAddress ) pipeTo self
Initializing
} ,
( ) )
2012-09-12 11:18:42 +02:00
when ( Initializing ) {
case Event ( Send ( msg , senderOption , recipient ) , _ ) ⇒
stash ( )
2012-12-07 16:03:04 +01:00
stay ( )
2012-12-20 12:54:43 +01:00
case Event ( Status . Failure ( e : InvalidAssociationException ) , _ ) ⇒
2012-09-12 11:18:42 +02:00
publishAndThrow ( new InvalidAssociation ( localAddress , remoteAddress , e ) )
2012-12-20 12:54:43 +01:00
case Event ( Status . Failure ( e ) , _ ) ⇒
2013-03-07 18:08:07 +01:00
publishAndThrow ( new EndpointAssociationException ( s" Association failed with [ $remoteAddress ] " , e ) )
2012-12-20 12:54:43 +01:00
case Event ( inboundHandle : AssociationHandle , _ ) ⇒
2012-12-21 18:11:56 +01:00
// Assert handle == None?
2012-12-07 16:03:04 +01:00
handle = Some ( inboundHandle )
2012-12-21 18:11:56 +01:00
reader = startReadEndpoint ( inboundHandle )
2012-09-12 11:18:42 +02:00
goto ( Writing )
}
when ( Buffering ) {
case Event ( Send ( msg , senderOption , recipient ) , _ ) ⇒
stash ( )
2012-12-07 16:03:04 +01:00
stay ( )
2012-09-12 11:18:42 +02:00
case Event ( BackoffTimer , _ ) ⇒ goto ( Writing )
2013-02-25 12:11:39 +01:00
case Event ( FlushAndStop , _ ) ⇒
stash ( ) // Flushing is postponed after the pending writes
stay ( )
2012-09-12 11:18:42 +02:00
}
when ( Writing ) {
case Event ( Send ( msg , senderOption , recipient ) , _ ) ⇒
2012-12-21 18:11:56 +01:00
try {
2012-12-14 13:45:55 +01:00
handle match {
2012-12-21 18:11:56 +01:00
case Some ( h ) ⇒
val pdu = codec . constructMessage ( recipient . localAddressToUse , recipient , serializeMessage ( msg ) , senderOption )
2013-03-20 20:38:49 +13:00
if ( pdu . size > transport . maximumPayloadBytes ) {
publishAndStay ( new OversizedPayloadException ( s" Discarding oversized payload sent to ${ recipient } : max allowed size ${ transport . maximumPayloadBytes } bytes, actual size of encoded ${ msg . getClass } was ${ pdu . size } bytes. " ) )
} else if ( h . write ( pdu ) ) {
stay ( )
} else {
2012-12-21 18:11:56 +01:00
stash ( )
goto ( Buffering )
}
case None ⇒
throw new EndpointException ( "Internal error: Endpoint is in state Writing, but no association handle is present." )
2012-12-14 13:45:55 +01:00
}
2012-12-07 16:03:04 +01:00
} catch {
2013-03-20 20:38:49 +13:00
case e : NotSerializableException ⇒ publishAndStay ( e )
case e : EndpointException ⇒ publishAndThrow ( e )
case NonFatal ( e ) ⇒ publishAndThrow ( new EndpointException ( "Failed to write message to the transport" , e ) )
2012-09-12 11:18:42 +02:00
}
2013-02-25 12:11:39 +01:00
2013-03-20 20:38:49 +13:00
// We are in Writing state, so stash is empty, safe to stop here
2013-02-25 12:11:39 +01:00
case Event ( FlushAndStop , _ ) ⇒ stop ( )
2012-09-12 11:18:42 +02:00
}
2013-01-14 10:04:49 +01:00
when ( Handoff ) {
case Event ( Terminated ( _ ) , _ ) ⇒
reader = startReadEndpoint ( handle . get )
unstashAll ( )
goto ( Writing )
case Event ( Send ( msg , senderOption , recipient ) , _ ) ⇒
stash ( )
stay ( )
// TakeOver messages are not handled here but inside the whenUnhandled block, because the procedure is exactly the
// same. Any outstanding
}
2012-09-12 11:18:42 +02:00
whenUnhandled {
2013-03-07 18:08:07 +01:00
case Event ( Terminated ( r ) , _ ) if r == reader . orNull ⇒ publishAndThrow ( new EndpointDisassociatedException ( "Disassociated" ) )
2012-11-21 16:39:04 +01:00
case Event ( TakeOver ( newHandle ) , _ ) ⇒
// Shutdown old reader
2012-12-07 16:03:04 +01:00
handle foreach { _ . disassociate ( ) }
2013-01-14 10:04:49 +01:00
reader foreach context . stop
2012-12-07 16:03:04 +01:00
handle = Some ( newHandle )
2013-01-14 10:04:49 +01:00
goto ( Handoff )
2013-02-25 12:11:39 +01:00
case Event ( FlushAndStop , _ ) ⇒
stop ( )
2012-09-12 11:18:42 +02:00
}
onTransition {
case Initializing -> Writing ⇒
unstashAll ( )
eventPublisher . notifyListeners ( AssociatedEvent ( localAddress , remoteAddress , inbound ) )
2012-12-07 16:03:04 +01:00
case Writing -> Buffering ⇒
setTimer ( "backoff-timer" , BackoffTimer , settings . BackoffPeriod , repeat = false )
2012-09-12 11:18:42 +02:00
case Buffering -> Writing ⇒
unstashAll ( )
cancelTimer ( "backoff-timer" )
}
onTermination {
2012-12-18 13:46:10 +01:00
case StopEvent ( _ , _ , _ ) ⇒
2012-12-07 16:03:04 +01:00
// FIXME: Add a test case for this
// It is important to call unstashAll() for the stash to work properly and maintain messages during restart.
// As the FSM trait does not call super.postStop(), this call is needed
2012-11-21 16:39:04 +01:00
unstashAll ( )
2012-12-07 16:03:04 +01:00
handle foreach { _ . disassociate ( ) }
2012-09-12 11:18:42 +02:00
eventPublisher . notifyListeners ( DisassociatedEvent ( localAddress , remoteAddress , inbound ) )
}
2012-12-21 18:11:56 +01:00
private def startReadEndpoint ( handle : AssociationHandle ) : Some [ ActorRef ] = {
val newReader =
2013-03-20 20:38:49 +13:00
context . watch ( context . actorOf (
Props ( new EndpointReader ( localAddress , remoteAddress , transport , settings , codec , msgDispatch , inbound ) ) ,
2012-12-21 18:11:56 +01:00
"endpointReader-" + AddressUrlEncoder ( remoteAddress ) + "-" + readerId . next ( ) ) )
handle . readHandlerPromise . success ( ActorHandleEventListener ( newReader ) )
Some ( newReader )
2012-09-12 11:18:42 +02:00
}
2012-12-07 16:03:04 +01:00
private def serializeMessage ( msg : Any ) : MessageProtocol = handle match {
case Some ( h ) ⇒
2013-03-26 11:25:09 +01:00
Serialization . currentTransportInformation . withValue ( Serialization . Information ( h . localAddress , context . system ) ) {
2012-12-07 16:03:04 +01:00
( MessageSerializer . serialize ( extendedSystem , msg . asInstanceOf [ AnyRef ] ) )
}
case None ⇒ throw new EndpointException ( "Internal error: No handle was present during serialization of" +
2012-12-18 13:46:10 +01:00
"outbound message." )
2012-09-12 11:18:42 +02:00
}
}
2013-02-08 13:13:52 +01:00
/* *
* INTERNAL API
*/
2012-09-12 11:18:42 +02:00
private [ remote ] class EndpointReader (
2013-03-20 20:38:49 +13:00
localAddress : Address ,
remoteAddress : Address ,
transport : Transport ,
settings : RemoteSettings ,
codec : AkkaPduCodec ,
msgDispatch : InboundMessageDispatcher ,
val inbound : Boolean ) extends EndpointActor ( localAddress , remoteAddress , transport , settings , codec ) {
2012-09-12 11:18:42 +02:00
2012-12-11 13:08:36 +01:00
val provider = RARP ( context . system ) . provider
2012-09-12 11:18:42 +02:00
override def receive : Receive = {
case Disassociated ⇒ context . stop ( self )
2012-11-21 15:58:01 +01:00
case InboundPayload ( p ) ⇒
2013-03-20 20:38:49 +13:00
if ( p . size > transport . maximumPayloadBytes ) {
publishError ( new OversizedPayloadException ( s" Discarding oversized payload received: max allowed size ${ transport . maximumPayloadBytes } bytes, actual size ${ p . size } bytes. " ) )
} else {
val msg = decodePdu ( p )
msgDispatch . dispatch ( msg . recipient , msg . recipientAddress , msg . serializedMessage , msg . senderOption )
}
2012-09-12 11:18:42 +02:00
}
2012-11-21 15:58:01 +01:00
private def decodePdu ( pdu : ByteString ) : Message = try {
codec . decodeMessage ( pdu , provider , localAddress )
2012-09-12 11:18:42 +02:00
} catch {
case NonFatal ( e ) ⇒ throw new EndpointException ( "Error while decoding incoming Akka PDU" , e )
}
}