pekko/akka-remote/src/main/scala/akka/remote/Remoting.scala

631 lines
26 KiB
Scala
Raw Normal View History

/**
* Copyright (C) 2009-2013 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.remote
import akka.actor.SupervisorStrategy._
import akka.actor._
import akka.event.{ Logging, LoggingAdapter }
2012-12-13 14:27:34 +01:00
import akka.japi.Util.immutableSeq
import akka.pattern.{ AskTimeoutException, gracefulStop, pipe, ask }
2012-12-13 14:27:34 +01:00
import akka.remote.EndpointManager._
import akka.remote.Remoting.TransportSupervisor
import akka.remote.transport.Transport.{ ActorAssociationEventListener, AssociationEventListener, InboundAssociation }
import akka.remote.transport._
import akka.util.Timeout
2012-12-13 14:27:34 +01:00
import com.typesafe.config.Config
import java.net.URLEncoder
import java.util.concurrent.TimeoutException
import scala.collection.immutable.{ Seq, HashMap }
import scala.concurrent.duration._
import scala.concurrent.{ Promise, Await, Future }
import scala.util.control.NonFatal
import scala.util.{ Failure, Success }
import akka.remote.transport.AkkaPduCodec.Message
import java.util.concurrent.ConcurrentHashMap
/**
* INTERNAL API
*/
private[remote] object AddressUrlEncoder {
def apply(address: Address): String = URLEncoder.encode(address.toString, "utf-8")
}
/**
* INTERNAL API
*/
private[remote] case class RARP(provider: RemoteActorRefProvider) extends Extension
/**
* INTERNAL API
*/
private[remote] object RARP extends ExtensionId[RARP] with ExtensionIdProvider {
override def lookup() = RARP
override def createExtension(system: ExtendedActorSystem) = RARP(system.provider.asInstanceOf[RemoteActorRefProvider])
}
/**
* INTERNAL API
*/
private[remote] object Remoting {
2012-11-22 13:33:48 +01:00
final val EndpointManagerName = "endpointManager"
def localAddressForRemote(transportMapping: Map[String, Set[(Transport, Address)]], remote: Address): Address = {
transportMapping.get(remote.protocol) match {
case Some(transports)
2012-11-22 13:33:48 +01:00
val responsibleTransports = transports.filter { case (t, _) t.isResponsibleFor(remote) }
responsibleTransports.size match {
case 0
throw new RemoteTransportException(
s"No transport is responsible for address: [$remote] although protocol [${remote.protocol}] is available." +
" Make sure at least one transport is configured to be responsible for the address.",
null)
case 1
responsibleTransports.head._2
case _
throw new RemoteTransportException(
s"Multiple transports are available for [$remote]: [${responsibleTransports.mkString(",")}]. " +
"Remoting cannot decide which transport to use to reach the remote system. Change your configuration " +
"so that only one transport is responsible for the address.",
null)
}
case None throw new RemoteTransportException(
s"No transport is loaded for protocol: [${remote.protocol}], available protocols: [${transportMapping.keys.mkString(", ")}]", null)
}
}
case class RegisterTransportActor(props: Props, name: String)
private[Remoting] class TransportSupervisor extends Actor {
override def supervisorStrategy = OneForOneStrategy() {
case NonFatal(e) Restart
}
def receive = {
case RegisterTransportActor(props, name) sender ! context.actorOf(props, name)
}
}
}
/**
* INTERNAL API
*/
private[remote] class Remoting(_system: ExtendedActorSystem, _provider: RemoteActorRefProvider) extends RemoteTransport(_system, _provider) {
@volatile private var endpointManager: Option[ActorRef] = None
@volatile private var transportMapping: Map[String, Set[(Transport, Address)]] = _
// This is effectively a write-once variable similar to a lazy val. The reason for not using a lazy val is exception
// handling.
@volatile var addresses: Set[Address] = _
// This variable has the same semantics as the addresses variable, in the sense it is written once, and emulates
// a lazy val
@volatile var defaultAddress: Address = _
import provider.remoteSettings._
val transportSupervisor = system.asInstanceOf[ActorSystemImpl].systemActorOf(Props[TransportSupervisor], "transports")
override def localAddressForRemote(remote: Address): Address = Remoting.localAddressForRemote(transportMapping, remote)
val log: LoggingAdapter = Logging(system.eventStream, "Remoting")
val eventPublisher = new EventPublisher(system, log, LogRemoteLifecycleEvents)
private def notifyError(msg: String, cause: Throwable): Unit =
eventPublisher.notifyListeners(RemotingErrorEvent(new RemoteTransportException(msg, cause)))
override def shutdown(): Future[Unit] = {
import scala.concurrent.ExecutionContext.Implicits.global
endpointManager match {
case Some(manager)
implicit val timeout = ShutdownTimeout
val stopped: Future[Boolean] = (manager ? ShutdownAndFlush).mapTo[Boolean]
def finalize(): Unit = {
2012-12-13 14:27:34 +01:00
eventPublisher.notifyListeners(RemotingShutdownEvent)
endpointManager = None
}
stopped.onComplete {
case Success(flushSuccessful)
if (!flushSuccessful)
log.warning("Shutdown finished, but flushing timed out. Some messages might not have been sent. " +
"Increase akka.remote.flush-wait-on-shutdown to a larger value to avoid this.")
finalize()
case Failure(e)
notifyError("Failure during shutdown of remoting.", e)
finalize()
}
stopped map { _ () } // RARP needs only type Unit, not a boolean
case None
log.warning("Remoting is not running. Ignoring shutdown attempt.")
Future successful (())
}
}
// Start assumes that it cannot be followed by another start() without having a shutdown() first
override def start(): Unit = {
endpointManager match {
case None
log.info("Starting remoting")
val manager: ActorRef = system.asInstanceOf[ActorSystemImpl].systemActorOf(
Props(classOf[EndpointManager], provider.remoteSettings.config, log), Remoting.EndpointManagerName)
endpointManager = Some(manager)
try {
val addressesPromise: Promise[Seq[(Transport, Address)]] = Promise()
manager ! Listen(addressesPromise)
val transports: Seq[(Transport, Address)] = Await.result(addressesPromise.future,
StartupTimeout.duration)
if (transports.isEmpty) throw new RemoteTransportException("No transport drivers were loaded.", null)
transportMapping = transports.groupBy {
case (transport, _) transport.schemeIdentifier
} map { case (k, v) k -> v.toSet }
defaultAddress = transports.head._2
addresses = transports.map { _._2 }.toSet
2012-12-07 16:03:04 +01:00
log.info("Remoting started; listening on addresses :" + addresses.mkString("[", ", ", "]"))
manager ! StartupFinished
eventPublisher.notifyListeners(RemotingListenEvent(addresses))
} catch {
case e: TimeoutException
notifyError("Startup timed out", e)
throw e
case NonFatal(e)
notifyError("Startup failed", e)
throw e
}
case Some(_)
log.warning("Remoting was already started. Ignoring start attempt.")
}
}
override def send(message: Any, senderOption: Option[ActorRef], recipient: RemoteActorRef): Unit = endpointManager match {
case Some(manager) manager.tell(Send(message, senderOption, recipient), sender = senderOption getOrElse Actor.noSender)
case None throw new RemoteTransportExceptionNoStackTrace("Attempted to send remote message but Remoting is not running.", null)
}
override def managementCommand(cmd: Any): Future[Boolean] = endpointManager match {
case Some(manager)
import system.dispatcher
implicit val timeout = CommandAckTimeout
manager ? ManagementCommand(cmd) map { case ManagementCommandAck(status) status }
case None throw new RemoteTransportExceptionNoStackTrace("Attempted to send management command but Remoting is not running.", null)
}
2013-04-18 17:35:43 +02:00
override def quarantine(remoteAddress: Address, uid: Int): Unit = endpointManager match {
case Some(manager) manager ! Quarantine(remoteAddress, uid)
case _ throw new RemoteTransportExceptionNoStackTrace(
s"Attempted to quarantine addres [$remoteAddress] with uid [$uid] but Remoting is not running", null)
}
// Not used anywhere only to keep compatibility with RemoteTransport interface
protected def useUntrustedMode: Boolean = provider.remoteSettings.UntrustedMode
// Not used anywhere only to keep compatibility with RemoteTransport interface
protected def logRemoteLifeCycleEvents: Boolean = LogRemoteLifecycleEvents
}
/**
* INTERNAL API
*/
private[remote] object EndpointManager {
2012-12-13 14:27:34 +01:00
// Messages between Remoting and EndpointManager
sealed trait RemotingCommand
case class Listen(addressesPromise: Promise[Seq[(Transport, Address)]]) extends RemotingCommand
2012-11-22 13:33:48 +01:00
case object StartupFinished extends RemotingCommand
2012-12-13 14:27:34 +01:00
case object ShutdownAndFlush extends RemotingCommand
case class Send(message: Any, senderOption: Option[ActorRef], recipient: RemoteActorRef, seqOpt: Option[SeqNo] = None)
extends RemotingCommand with HasSequenceNumber {
override def toString = s"Remote message $senderOption -> $recipient"
// This MUST throw an exception to indicate that we attempted to put a nonsequenced message in one of the
// acknowledged delivery buffers
def seq = seqOpt.get
}
2013-04-18 17:35:43 +02:00
case class Quarantine(remoteAddress: Address, uid: Int) extends RemotingCommand
case class ManagementCommand(cmd: Any) extends RemotingCommand
case class ManagementCommandAck(status: Boolean)
2012-12-13 14:27:34 +01:00
// Messages internal to EndpointManager
case object Prune
case class ListensResult(addressesPromise: Promise[Seq[(Transport, Address)]],
results: Seq[(Transport, Address, Promise[AssociationEventListener])])
// Helper class to store address pairs
case class Link(localAddress: Address, remoteAddress: Address)
sealed trait EndpointPolicy {
/**
* Indicates that the policy does not contain an active endpoint, but it is a tombstone of a previous failure
*/
def isTombstone: Boolean
}
case class Pass(endpoint: ActorRef) extends EndpointPolicy {
override def isTombstone: Boolean = false
}
case class Gated(timeOfRelease: Deadline) extends EndpointPolicy {
override def isTombstone: Boolean = true
}
2013-04-18 17:35:43 +02:00
case class Quarantined(uid: Int, timeOfRelease: Deadline) extends EndpointPolicy {
override def isTombstone: Boolean = true
}
// Not threadsafe -- only to be used in HeadActor
class EndpointRegistry {
private var addressToWritable = HashMap[Address, EndpointPolicy]()
private var writableToAddress = HashMap[ActorRef, Address]()
private var addressToReadonly = HashMap[Address, ActorRef]()
private var readonlyToAddress = HashMap[ActorRef, Address]()
def registerWritableEndpoint(address: Address, endpoint: ActorRef): ActorRef = addressToWritable.get(address) match {
case Some(Pass(e))
throw new IllegalArgumentException(s"Attempting to overwrite existing endpoint [$e] with [$endpoint]")
case _
addressToWritable += address -> Pass(endpoint)
writableToAddress += endpoint -> address
endpoint
}
def registerReadOnlyEndpoint(address: Address, endpoint: ActorRef): ActorRef = {
addressToReadonly += address -> endpoint
readonlyToAddress += endpoint -> address
endpoint
2012-11-22 13:33:48 +01:00
}
def unregisterEndpoint(endpoint: ActorRef): Unit =
if (isWritable(endpoint)) {
val address = writableToAddress(endpoint)
addressToWritable.get(address) match {
case Some(policy) if policy.isTombstone // There is already a tombstone directive, leave it there
case _ addressToWritable -= address
}
writableToAddress -= endpoint
} else if (isReadOnly(endpoint)) {
addressToReadonly -= readonlyToAddress(endpoint)
readonlyToAddress -= endpoint
}
def writableEndpointWithPolicyFor(address: Address): Option[EndpointPolicy] = addressToWritable.get(address)
def hasWritableEndpointFor(address: Address): Boolean = writableEndpointWithPolicyFor(address) match {
case Some(Pass(_)) true
case _ false
}
def readOnlyEndpointFor(address: Address): Option[ActorRef] = addressToReadonly.get(address)
def isWritable(endpoint: ActorRef): Boolean = writableToAddress contains endpoint
def isReadOnly(endpoint: ActorRef): Boolean = readonlyToAddress contains endpoint
2013-04-18 17:35:43 +02:00
def isQuarantined(address: Address, uid: Int): Boolean = writableEndpointWithPolicyFor(address) match {
case Some(Quarantined(`uid`, timeOfRelease)) timeOfRelease.hasTimeLeft()
case _ false
}
def markAsFailed(endpoint: ActorRef, timeOfRelease: Deadline): Unit =
if (isWritable(endpoint)) {
addressToWritable += writableToAddress(endpoint) -> Gated(timeOfRelease)
writableToAddress -= endpoint
} else if (isReadOnly(endpoint)) {
addressToReadonly -= readonlyToAddress(endpoint)
readonlyToAddress -= endpoint
}
2013-04-18 17:35:43 +02:00
def markAsQuarantined(address: Address, uid: Int, timeOfRelease: Deadline): Unit =
addressToWritable += address -> Quarantined(uid, timeOfRelease)
def allEndpoints: collection.Iterable[ActorRef] = writableToAddress.keys ++ readonlyToAddress.keys
2013-04-18 17:35:43 +02:00
def prune(): Unit = {
addressToWritable = addressToWritable.filter {
2013-04-18 17:35:43 +02:00
case (_, Gated(timeOfRelease)) timeOfRelease.hasTimeLeft
case (_, Quarantined(_, timeOfRelease)) timeOfRelease.hasTimeLeft
case _ true
}
}
}
}
/**
* INTERNAL API
*/
private[remote] class EndpointManager(conf: Config, log: LoggingAdapter) extends Actor {
import EndpointManager._
import context.dispatcher
val settings = new RemoteSettings(conf)
val extendedSystem = context.system.asInstanceOf[ExtendedActorSystem]
2012-11-22 13:33:48 +01:00
val endpointId: Iterator[Int] = Iterator from 0
val eventPublisher = new EventPublisher(context.system, log, settings.LogRemoteLifecycleEvents)
// Mapping between addresses and endpoint actors. If passive connections are turned off, incoming connections
// will be not part of this map!
val endpoints = new EndpointRegistry
// Mapping between transports and the local addresses they listen to
var transportMapping: Map[Address, Transport] = Map()
def retryGateEnabled = settings.RetryGateClosedFor > Duration.Zero
val pruneInterval: FiniteDuration = if (retryGateEnabled) settings.RetryGateClosedFor * 2 else Duration.Zero
2012-11-22 13:33:48 +01:00
val pruneTimerCancellable: Option[Cancellable] = if (retryGateEnabled)
Some(context.system.scheduler.schedule(pruneInterval, pruneInterval, self, Prune))
else None
override val supervisorStrategy =
OneForOneStrategy(settings.MaximumRetriesInWindow, settings.RetryWindow, loggingEnabled = false) {
case InvalidAssociation(localAddress, remoteAddress, _)
log.error("Tried to associate with invalid remote address [{}]. " +
"Address is now quarantined, all messages to this address will be delivered to dead letters.", remoteAddress)
endpoints.markAsFailed(sender, Deadline.now + settings.UnknownAddressGateClosedFor)
context.system.eventStream.publish(AddressTerminated(remoteAddress))
Stop
2013-04-18 17:35:43 +02:00
case HopelessAssociation(localAddress, remoteAddress, Some(uid), _)
settings.QuarantineDuration match {
case d: FiniteDuration
log.error("Association to [{}] having UID [{}] is irrecoverably failed. UID is now quarantined and all " +
"messages to this UID will be delivered to dead letters. Remote actorsystem must be restarted to recover " +
"from this situation.", remoteAddress, uid)
endpoints.markAsQuarantined(remoteAddress, uid, Deadline.now + d)
case _ // disabled
2013-04-18 17:35:43 +02:00
}
context.system.eventStream.publish(AddressTerminated(remoteAddress))
Stop
2013-04-18 17:35:43 +02:00
case HopelessAssociation(localAddress, remoteAddress, None, _)
settings.QuarantineDuration match {
case d: FiniteDuration
log.error("Association to [{}] with unknown UID is irrecoverably failed. " +
"Address is now quarantined, all messages to this address will be delivered to dead letters.", remoteAddress)
endpoints.markAsFailed(sender, Deadline.now + d)
case _
2013-04-18 17:35:43 +02:00
}
context.system.eventStream.publish(AddressTerminated(remoteAddress))
Stop
case _: QuarantinedUidException
Stop
case NonFatal(e)
// logging
e match {
case _: EndpointDisassociatedException | _: EndpointAssociationException // no logging
case _ log.error(e, e.getMessage)
}
Stop
}
// Structure for saving reliable delivery state across restarts of Endpoints
val receiveBuffers = new ConcurrentHashMap[Link, AckedReceiveBuffer[Message]]()
def receive = {
case Listen(addressesPromise)
listens map { ListensResult(addressesPromise, _) } pipeTo self
case ListensResult(addressesPromise, results)
transportMapping = results.groupBy {
case (_, transportAddress, _) transportAddress
} map {
case (a, t) if t.size > 1
throw new RemoteTransportException(s"There are more than one transports listening on local address [$a]", null)
case (a, t) a -> t.head._1
}
// Register to each transport as listener and collect mapping to addresses
val transportsAndAddresses = results map {
case (transport, address, promise)
promise.success(ActorAssociationEventListener(self))
transport -> address
}
addressesPromise.success(transportsAndAddresses)
case ManagementCommand(_)
sender ! ManagementCommandAck(false)
2013-04-19 13:36:39 +02:00
case StartupFinished
context.become(accepting)
case ShutdownAndFlush
sender ! true
context.stop(self) // Nothing to flush at this point
}
val accepting: Receive = {
case ManagementCommand(cmd)
val allStatuses = transportMapping.values map { transport
transport.managementCommand(cmd)
}
Future.fold(allStatuses)(true)(_ && _) map ManagementCommandAck pipeTo sender
case Quarantine(address, uid)
settings.QuarantineDuration match {
case d: FiniteDuration
// Stop writers
endpoints.writableEndpointWithPolicyFor(address) match {
case Some(Pass(endpoint)) context.stop(endpoint)
case _ // nothing to stop
}
// Stop inbound read-only associations
endpoints.readOnlyEndpointFor(address) match {
case Some(endpoint) context.stop(endpoint)
case _ // nothing to stop
}
endpoints.markAsQuarantined(address, uid, Deadline.now + d)
case _ // Ignore
}
case s @ Send(message, senderOption, recipientRef, _)
val recipientAddress = recipientRef.path.address
2013-04-18 17:35:43 +02:00
def createAndRegisterWritingEndpoint(refuseUid: Option[Int]): ActorRef = endpoints.registerWritableEndpoint(recipientAddress, createEndpoint(
recipientAddress,
recipientRef.localAddressToUse,
transportMapping(recipientRef.localAddressToUse),
settings,
2013-04-18 17:35:43 +02:00
handleOption = None,
refuseUid,
writing = true))
endpoints.writableEndpointWithPolicyFor(recipientAddress) match {
case Some(Pass(endpoint))
endpoint ! s
case Some(Gated(timeOfRelease))
2013-04-18 17:35:43 +02:00
if (timeOfRelease.isOverdue()) createAndRegisterWritingEndpoint(None) ! s
else extendedSystem.deadLetters ! s
2013-04-18 17:35:43 +02:00
case Some(Quarantined(uid, timeOfRelease))
2013-04-19 15:31:03 +02:00
if (timeOfRelease.isOverdue()) createAndRegisterWritingEndpoint(None) ! s
else createAndRegisterWritingEndpoint(Some(uid)) ! s
case None
2013-04-18 17:35:43 +02:00
createAndRegisterWritingEndpoint(None) ! s
}
case InboundAssociation(handle: AkkaProtocolHandle) endpoints.readOnlyEndpointFor(handle.remoteAddress) match {
case Some(endpoint) endpoint ! EndpointWriter.TakeOver(handle)
case None
2013-04-18 17:35:43 +02:00
if (endpoints.isQuarantined(handle.remoteAddress, handle.handshakeInfo.uid)) handle.disassociate()
else {
val writing = settings.UsePassiveConnections && !endpoints.hasWritableEndpointFor(handle.remoteAddress)
eventPublisher.notifyListeners(AssociatedEvent(handle.localAddress, handle.remoteAddress, true))
val endpoint = createEndpoint(
handle.remoteAddress,
handle.localAddress,
transportMapping(handle.localAddress),
settings,
Some(handle),
2013-04-18 17:35:43 +02:00
refuseUid = None,
writing)
if (writing)
endpoints.registerWritableEndpoint(handle.remoteAddress, endpoint)
else
endpoints.registerReadOnlyEndpoint(handle.remoteAddress, endpoint)
}
}
2012-12-13 14:27:34 +01:00
case Terminated(endpoint)
endpoints.unregisterEndpoint(endpoint)
2012-12-13 14:27:34 +01:00
case Prune
2013-04-18 17:35:43 +02:00
endpoints.prune()
2012-12-13 14:27:34 +01:00
case ShutdownAndFlush
// Shutdown all endpoints and signal to sender when ready (and whether all endpoints were shut down gracefully)
val sys = context.system // Avoid closing over context
Future sequence endpoints.allEndpoints.map {
gracefulStop(_, settings.FlushWait, EndpointWriter.FlushAndStop)
} map { _.foldLeft(true) { _ && _ } } recover {
case _: AskTimeoutException false
} pipeTo sender
2012-12-13 14:27:34 +01:00
// Ignore all other writes
context.become(flushing)
}
def flushing: Receive = {
case s: Send extendedSystem.deadLetters ! s
2012-12-13 14:27:34 +01:00
case InboundAssociation(h) h.disassociate()
case Terminated(_) // why should we care now?
2012-12-13 14:27:34 +01:00
}
private def listens: Future[Seq[(Transport, Address, Promise[AssociationEventListener])]] = {
/*
* Constructs chains of adapters on top of each driver as given in configuration. The resulting structure looks
* like the following:
* AkkaProtocolTransport <- Adapter <- ... <- Adapter <- Driver
*
* The transports variable contains only the heads of each chains (the AkkaProtocolTransport instances).
*/
val transports: Seq[AkkaProtocolTransport] = for ((fqn, adapters, config) settings.Transports) yield {
val args = Seq(classOf[ExtendedActorSystem] -> context.system, classOf[Config] -> config)
// Loads the driver -- the bottom element of the chain.
// The chain at this point:
// Driver
val driver = extendedSystem.dynamicAccess
.createInstanceFor[Transport](fqn, args).recover({
case exception throw new IllegalArgumentException(
(s"Cannot instantiate transport [$fqn]. " +
"Make sure it extends [akka.remote.transport.Transport] and has constructor with " +
"[akka.actor.ExtendedActorSystem] and [com.typesafe.config.Config] parameters"), exception)
}).get
// Iteratively decorates the bottom level driver with a list of adapters.
// The chain at this point:
// Adapter <- ... <- Adapter <- Driver
val wrappedTransport =
adapters.map { TransportAdaptersExtension.get(context.system).getAdapterProvider(_) }.foldLeft(driver) {
(t: Transport, provider: TransportAdapterProvider)
// The TransportAdapterProvider will wrap the given Transport and returns with a wrapped one
provider.create(t, context.system.asInstanceOf[ExtendedActorSystem])
}
// Apply AkkaProtocolTransport wrapper to the end of the chain
// The chain at this point:
// AkkaProtocolTransport <- Adapter <- ... <- Adapter <- Driver
new AkkaProtocolTransport(wrappedTransport, context.system, new AkkaProtocolSettings(conf), AkkaPduProtobufCodec)
}
// Collect all transports, listen addresses and listener promises in one future
Future.sequence(transports.map { transport
transport.listen map { case (address, listenerPromise) (transport, address, listenerPromise) }
})
}
private def createEndpoint(remoteAddress: Address,
localAddress: Address,
transport: Transport,
endpointSettings: RemoteSettings,
handleOption: Option[AkkaProtocolHandle],
2013-04-18 17:35:43 +02:00
refuseUid: Option[Int],
writing: Boolean): ActorRef = {
2012-12-07 16:03:04 +01:00
assert(transportMapping contains localAddress)
if (writing) context.watch(context.actorOf(ReliableDeliverySupervisor(
handleOption,
localAddress,
remoteAddress,
transport,
endpointSettings,
AkkaPduProtobufCodec,
2013-04-18 17:35:43 +02:00
refuseUid,
receiveBuffers).withDispatcher("akka.remote.writer-dispatcher"),
"reliableEndpointWriter-" + AddressUrlEncoder(remoteAddress) + "-" + endpointId.next()))
else context.watch(context.actorOf(EndpointWriter(
handleOption,
localAddress,
remoteAddress,
transport,
endpointSettings,
AkkaPduProtobufCodec,
2013-04-18 17:35:43 +02:00
refuseUid,
receiveBuffers,
reliableDeliverySupervisor = None).withDispatcher("akka.remote.writer-dispatcher"),
"endpointWriter-" + AddressUrlEncoder(remoteAddress) + "-" + endpointId.next()))
}
override def postStop(): Unit = {
pruneTimerCancellable.foreach { _.cancel() }
transportMapping.values foreach { transport
2012-11-22 13:33:48 +01:00
try transport.shutdown() catch {
case NonFatal(e)
2012-12-07 16:03:04 +01:00
log.error(e, s"Unable to shut down the underlying transport: [$transport]")
}
}
}
}