* fix memory leak in SystemMessageDelivery * initial set of tests for idle outbound associations, credit to mboogerd * close inbound compression when quarantined, #23967 * make sure compressions for quarantined are removed in case they are lingering around * also means that advertise will not be done for quarantined * remove tombstone in InboundCompressions * simplify async callbacks by using invokeWithFeedback * compression for old incarnation, #24400 * it was fixed by the other previous changes * also confirmed by running the SimpleClusterApp with TCP as described in the ticket * test with tcp and tls-tcp transport * handle the stop signals differently for tcp transport because they are converted to StreamTcpException * cancel timers on shutdown * share the top-level FR for all Association instances * use linked queue for control and large streams, less memory usage * remove quarantined idle Association completely after a configured delay * note that shallow Association instances may still lingering in the heap because of cached references from RemoteActorRef, which may be cached by LruBoundedCache (used by resolve actor ref). Those are small, since the queues have been removed, and the cache is bounded.
216 lines
6.7 KiB
Scala
216 lines
6.7 KiB
Scala
/**
|
|
* Copyright (C) 2016-2018 Lightbend Inc. <https://www.lightbend.com>
|
|
*/
|
|
package akka.remote.artery
|
|
|
|
import java.util.ArrayDeque
|
|
|
|
import scala.concurrent.Future
|
|
import scala.concurrent.Promise
|
|
import scala.util.Try
|
|
|
|
import akka.Done
|
|
import akka.stream.Attributes
|
|
import akka.stream.FlowShape
|
|
import akka.stream.Inlet
|
|
import akka.stream.Outlet
|
|
import akka.stream.stage._
|
|
import akka.remote.UniqueAddress
|
|
import akka.util.OptionVal
|
|
import akka.event.Logging
|
|
|
|
/** INTERNAL API: marker trait for protobuf-serializable artery messages */
|
|
private[remote] trait ArteryMessage extends Serializable
|
|
|
|
/**
|
|
* INTERNAL API: Marker trait for reply messages
|
|
*/
|
|
private[remote] trait Reply extends ControlMessage
|
|
|
|
/**
|
|
* INTERNAL API
|
|
* Marker trait for control messages that can be sent via the system message sub-channel
|
|
* but don't need full reliable delivery. E.g. `HandshakeReq` and `Reply`.
|
|
*/
|
|
private[remote] trait ControlMessage extends ArteryMessage
|
|
|
|
/**
|
|
* INTERNAL API
|
|
*/
|
|
private[remote] final case class Quarantined(from: UniqueAddress, to: UniqueAddress) extends ControlMessage
|
|
|
|
/**
|
|
* INTERNAL API
|
|
*/
|
|
private[remote] case class ActorSystemTerminating(from: UniqueAddress) extends ControlMessage
|
|
|
|
/**
|
|
* INTERNAL API
|
|
*/
|
|
private[remote] case class ActorSystemTerminatingAck(from: UniqueAddress) extends ArteryMessage
|
|
|
|
/**
|
|
* INTERNAL API
|
|
*/
|
|
private[remote] object InboundControlJunction {
|
|
|
|
/**
|
|
* Observer subject for inbound control messages.
|
|
* Interested observers can attach themselves to the
|
|
* subject to get notification of incoming control
|
|
* messages.
|
|
*/
|
|
private[remote] trait ControlMessageSubject {
|
|
def attach(observer: ControlMessageObserver): Future[Done]
|
|
def detach(observer: ControlMessageObserver): Unit
|
|
}
|
|
|
|
private[remote] trait ControlMessageObserver {
|
|
|
|
/**
|
|
* Notification of incoming control message. The message
|
|
* of the envelope is always a `ControlMessage`.
|
|
*/
|
|
def notify(inboundEnvelope: InboundEnvelope): Unit
|
|
|
|
def controlSubjectCompleted(signal: Try[Done]): Unit
|
|
}
|
|
|
|
// messages for the stream callback
|
|
private[InboundControlJunction] sealed trait CallbackMessage
|
|
private[InboundControlJunction] final case class Attach(observer: ControlMessageObserver, done: Promise[Done])
|
|
extends CallbackMessage
|
|
private[InboundControlJunction] final case class Dettach(observer: ControlMessageObserver) extends CallbackMessage
|
|
}
|
|
|
|
/**
|
|
* INTERNAL API
|
|
*/
|
|
private[remote] class InboundControlJunction
|
|
extends GraphStageWithMaterializedValue[FlowShape[InboundEnvelope, InboundEnvelope], InboundControlJunction.ControlMessageSubject] {
|
|
import InboundControlJunction._
|
|
|
|
val in: Inlet[InboundEnvelope] = Inlet("InboundControlJunction.in")
|
|
val out: Outlet[InboundEnvelope] = Outlet("InboundControlJunction.out")
|
|
override val shape: FlowShape[InboundEnvelope, InboundEnvelope] = FlowShape(in, out)
|
|
|
|
override def createLogicAndMaterializedValue(inheritedAttributes: Attributes) = {
|
|
val logic = new GraphStageLogic(shape) with InHandler with OutHandler with ControlMessageSubject {
|
|
|
|
private var observers: Vector[ControlMessageObserver] = Vector.empty
|
|
|
|
private val callback = getAsyncCallback[CallbackMessage] {
|
|
case Attach(observer, done) ⇒
|
|
observers :+= observer
|
|
done.success(Done)
|
|
case Dettach(observer) ⇒
|
|
observers = observers.filterNot(_ == observer)
|
|
}
|
|
|
|
override def postStop(): Unit = {
|
|
observers.foreach(_.controlSubjectCompleted(Try(Done)))
|
|
observers = Vector.empty
|
|
}
|
|
|
|
// InHandler
|
|
override def onPush(): Unit = {
|
|
grab(in) match {
|
|
case env: InboundEnvelope if env.message.isInstanceOf[ControlMessage] ⇒
|
|
observers.foreach(_.notify(env))
|
|
pull(in)
|
|
case env ⇒
|
|
push(out, env)
|
|
}
|
|
}
|
|
|
|
// OutHandler
|
|
override def onPull(): Unit = pull(in)
|
|
|
|
setHandlers(in, out, this)
|
|
|
|
// ControlMessageSubject impl
|
|
override def attach(observer: ControlMessageObserver): Future[Done] = {
|
|
val p = Promise[Done]()
|
|
callback.invoke(Attach(observer, p))
|
|
p.future
|
|
}
|
|
|
|
override def detach(observer: ControlMessageObserver): Unit =
|
|
callback.invoke(Dettach(observer))
|
|
|
|
}
|
|
|
|
(logic, logic)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* INTERNAL API
|
|
*/
|
|
private[remote] object OutboundControlJunction {
|
|
private[remote] trait OutboundControlIngress {
|
|
def sendControlMessage(message: ControlMessage): Unit
|
|
}
|
|
}
|
|
|
|
/**
|
|
* INTERNAL API
|
|
*/
|
|
private[remote] class OutboundControlJunction(
|
|
outboundContext: OutboundContext, outboundEnvelopePool: ObjectPool[ReusableOutboundEnvelope])
|
|
extends GraphStageWithMaterializedValue[FlowShape[OutboundEnvelope, OutboundEnvelope], OutboundControlJunction.OutboundControlIngress] {
|
|
import OutboundControlJunction._
|
|
val in: Inlet[OutboundEnvelope] = Inlet("OutboundControlJunction.in")
|
|
val out: Outlet[OutboundEnvelope] = Outlet("OutboundControlJunction.out")
|
|
override val shape: FlowShape[OutboundEnvelope, OutboundEnvelope] = FlowShape(in, out)
|
|
|
|
override def createLogicAndMaterializedValue(inheritedAttributes: Attributes) = {
|
|
|
|
val logic = new GraphStageLogic(shape) with InHandler with OutHandler with StageLogging with OutboundControlIngress {
|
|
import OutboundControlJunction._
|
|
|
|
val sendControlMessageCallback = getAsyncCallback[ControlMessage](internalSendControlMessage)
|
|
private val maxControlMessageBufferSize: Int = outboundContext.settings.Advanced.OutboundControlQueueSize
|
|
private val buffer = new ArrayDeque[OutboundEnvelope]
|
|
|
|
// InHandler
|
|
override def onPush(): Unit = {
|
|
if (buffer.isEmpty && isAvailable(out))
|
|
push(out, grab(in))
|
|
else
|
|
buffer.offer(grab(in))
|
|
}
|
|
|
|
// OutHandler
|
|
override def onPull(): Unit = {
|
|
if (buffer.isEmpty && !hasBeenPulled(in))
|
|
pull(in)
|
|
else if (!buffer.isEmpty)
|
|
push(out, buffer.poll())
|
|
}
|
|
|
|
private def internalSendControlMessage(message: ControlMessage): Unit = {
|
|
if (buffer.isEmpty && isAvailable(out))
|
|
push(out, wrap(message))
|
|
else if (buffer.size < maxControlMessageBufferSize)
|
|
buffer.offer(wrap(message))
|
|
else {
|
|
// it's alright to drop control messages
|
|
log.debug("Dropping control message [{}] due to full buffer.", Logging.messageClassName(message))
|
|
}
|
|
}
|
|
|
|
private def wrap(message: ControlMessage): OutboundEnvelope =
|
|
outboundEnvelopePool.acquire().init(
|
|
recipient = OptionVal.None, message = message, sender = OptionVal.None)
|
|
|
|
override def sendControlMessage(message: ControlMessage): Unit =
|
|
sendControlMessageCallback.invoke(message)
|
|
|
|
setHandlers(in, out, this)
|
|
}
|
|
|
|
(logic, logic)
|
|
}
|
|
|
|
}
|