2016-05-04 13:31:08 +02:00
|
|
|
/**
|
|
|
|
|
* Copyright (C) 2016 Lightbend Inc. <http://www.lightbend.com>
|
|
|
|
|
*/
|
|
|
|
|
package akka.remote.artery
|
|
|
|
|
|
2016-05-09 07:31:41 +02:00
|
|
|
import java.util.concurrent.ThreadLocalRandom
|
2016-06-08 10:04:30 +02:00
|
|
|
|
2016-05-04 13:31:08 +02:00
|
|
|
import scala.concurrent.Await
|
|
|
|
|
import scala.concurrent.duration._
|
|
|
|
|
import akka.NotUsed
|
|
|
|
|
import akka.actor.ActorIdentity
|
|
|
|
|
import akka.actor.ActorSystem
|
|
|
|
|
import akka.actor.Identify
|
|
|
|
|
import akka.actor.PoisonPill
|
|
|
|
|
import akka.actor.RootActorPath
|
2016-09-20 14:23:50 +03:00
|
|
|
import akka.remote.{ AddressUidExtension, RARP, UniqueAddress }
|
2016-05-04 13:31:08 +02:00
|
|
|
import akka.remote.artery.SystemMessageDelivery._
|
|
|
|
|
import akka.stream.ActorMaterializer
|
|
|
|
|
import akka.stream.ActorMaterializerSettings
|
|
|
|
|
import akka.stream.ThrottleMode
|
|
|
|
|
import akka.stream.scaladsl.Flow
|
|
|
|
|
import akka.stream.scaladsl.Sink
|
|
|
|
|
import akka.stream.scaladsl.Source
|
|
|
|
|
import akka.stream.testkit.scaladsl.TestSink
|
|
|
|
|
import akka.testkit.AkkaSpec
|
|
|
|
|
import akka.testkit.ImplicitSender
|
|
|
|
|
import akka.testkit.TestActors
|
|
|
|
|
import akka.testkit.TestProbe
|
|
|
|
|
import com.typesafe.config.ConfigFactory
|
2016-06-05 15:40:06 +02:00
|
|
|
import akka.util.OptionVal
|
2016-05-04 13:31:08 +02:00
|
|
|
|
|
|
|
|
object SystemMessageDeliverySpec {
|
|
|
|
|
|
2016-05-18 09:22:22 +02:00
|
|
|
val config = ConfigFactory.parseString(s"""
|
2016-06-23 18:11:56 +02:00
|
|
|
akka.loglevel=INFO
|
2016-05-04 13:31:08 +02:00
|
|
|
akka {
|
2016-06-10 15:04:13 +02:00
|
|
|
actor.provider = remote
|
2016-05-04 13:31:08 +02:00
|
|
|
remote.artery.enabled = on
|
2016-09-09 13:46:50 +03:00
|
|
|
remote.artery.canonical.hostname = localhost
|
|
|
|
|
remote.artery.canonical.port = 0
|
2016-05-04 13:31:08 +02:00
|
|
|
}
|
|
|
|
|
akka.actor.serialize-creators = off
|
|
|
|
|
akka.actor.serialize-messages = off
|
|
|
|
|
""")
|
|
|
|
|
|
2016-09-08 15:01:32 +02:00
|
|
|
case class TestSysMsg(s: String) extends SystemMessageDelivery.AckedDeliveryMessage
|
|
|
|
|
|
2016-05-04 13:31:08 +02:00
|
|
|
}
|
|
|
|
|
|
2016-05-18 09:22:22 +02:00
|
|
|
class SystemMessageDeliverySpec extends AkkaSpec(SystemMessageDeliverySpec.config) with ImplicitSender {
|
2016-05-04 13:31:08 +02:00
|
|
|
import SystemMessageDeliverySpec._
|
|
|
|
|
|
2016-05-09 07:31:41 +02:00
|
|
|
val addressA = UniqueAddress(
|
2016-06-08 10:04:30 +02:00
|
|
|
RARP(system).provider.getDefaultAddress,
|
2016-09-26 15:34:59 +02:00
|
|
|
AddressUidExtension(system).longAddressUid)
|
2016-05-18 09:22:22 +02:00
|
|
|
val systemB = ActorSystem("systemB", system.settings.config)
|
2016-05-09 07:31:41 +02:00
|
|
|
val addressB = UniqueAddress(
|
2016-06-08 10:04:30 +02:00
|
|
|
RARP(systemB).provider.getDefaultAddress,
|
2016-09-26 15:34:59 +02:00
|
|
|
AddressUidExtension(systemB).longAddressUid)
|
2016-05-09 07:31:41 +02:00
|
|
|
val rootB = RootActorPath(addressB.address)
|
2016-05-04 13:31:08 +02:00
|
|
|
val matSettings = ActorMaterializerSettings(system).withFuzzing(true)
|
|
|
|
|
implicit val mat = ActorMaterializer(matSettings)(system)
|
|
|
|
|
|
2016-06-29 17:09:33 +02:00
|
|
|
private val outboundEnvelopePool = ReusableOutboundEnvelope.createObjectPool(capacity = 16)
|
|
|
|
|
|
2016-05-04 13:31:08 +02:00
|
|
|
override def afterTermination(): Unit = shutdown(systemB)
|
|
|
|
|
|
2016-06-29 17:09:33 +02:00
|
|
|
private def send(sendCount: Int, resendInterval: FiniteDuration, outboundContext: OutboundContext): Source[OutboundEnvelope, NotUsed] = {
|
2016-06-08 12:40:40 +02:00
|
|
|
val deadLetters = TestProbe().ref
|
2016-05-04 13:31:08 +02:00
|
|
|
Source(1 to sendCount)
|
2016-09-08 15:01:32 +02:00
|
|
|
.map(n ⇒ outboundEnvelopePool.acquire().init(OptionVal.None, TestSysMsg("msg-" + n), OptionVal.None))
|
2016-06-08 12:40:40 +02:00
|
|
|
.via(new SystemMessageDelivery(outboundContext, deadLetters, resendInterval, maxBufferSize = 1000))
|
2016-05-04 13:31:08 +02:00
|
|
|
}
|
|
|
|
|
|
2016-06-29 17:09:33 +02:00
|
|
|
private def inbound(inboundContext: InboundContext): Flow[OutboundEnvelope, InboundEnvelope, NotUsed] = {
|
2016-06-09 09:16:44 +02:00
|
|
|
val recipient = OptionVal.None // not used
|
2016-06-29 17:09:33 +02:00
|
|
|
Flow[OutboundEnvelope]
|
|
|
|
|
.map(outboundEnvelope ⇒ outboundEnvelope.message match {
|
|
|
|
|
case sysEnv: SystemMessageEnvelope ⇒
|
2016-09-20 14:23:50 +03:00
|
|
|
InboundEnvelope(recipient, sysEnv, OptionVal.None, addressA.uid,
|
2016-06-10 13:04:23 +02:00
|
|
|
inboundContext.association(addressA.uid))
|
2016-06-29 17:09:33 +02:00
|
|
|
})
|
2016-05-04 13:31:08 +02:00
|
|
|
.async
|
2016-05-09 07:31:41 +02:00
|
|
|
.via(new SystemMessageAcker(inboundContext))
|
2016-05-04 13:31:08 +02:00
|
|
|
}
|
|
|
|
|
|
2016-06-29 17:09:33 +02:00
|
|
|
private def drop(dropSeqNumbers: Vector[Long]): Flow[OutboundEnvelope, OutboundEnvelope, NotUsed] = {
|
|
|
|
|
Flow[OutboundEnvelope]
|
2016-05-04 13:31:08 +02:00
|
|
|
.statefulMapConcat(() ⇒ {
|
|
|
|
|
var dropping = dropSeqNumbers
|
|
|
|
|
|
|
|
|
|
{
|
2016-06-29 17:09:33 +02:00
|
|
|
outboundEnvelope ⇒
|
|
|
|
|
outboundEnvelope.message match {
|
|
|
|
|
case SystemMessageEnvelope(_, seqNo, _) ⇒
|
|
|
|
|
val i = dropping.indexOf(seqNo)
|
|
|
|
|
if (i >= 0) {
|
|
|
|
|
dropping = dropping.updated(i, -1L)
|
|
|
|
|
Nil
|
|
|
|
|
} else
|
|
|
|
|
List(outboundEnvelope)
|
|
|
|
|
case _ ⇒ Nil
|
|
|
|
|
}
|
2016-05-04 13:31:08 +02:00
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-09 07:31:41 +02:00
|
|
|
private def randomDrop[T](dropRate: Double): Flow[T, T, NotUsed] = Flow[T].mapConcat { elem ⇒
|
2016-05-04 13:31:08 +02:00
|
|
|
if (ThreadLocalRandom.current().nextDouble() < dropRate) Nil
|
|
|
|
|
else List(elem)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"System messages" must {
|
|
|
|
|
|
|
|
|
|
"be delivered with real actors" in {
|
2016-06-23 18:11:56 +02:00
|
|
|
systemB.actorOf(TestActors.echoActorProps, "echo")
|
2016-05-04 13:31:08 +02:00
|
|
|
|
|
|
|
|
val remoteRef = {
|
|
|
|
|
system.actorSelection(rootB / "user" / "echo") ! Identify(None)
|
|
|
|
|
expectMsgType[ActorIdentity].ref.get
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
watch(remoteRef)
|
|
|
|
|
remoteRef ! PoisonPill
|
|
|
|
|
expectTerminated(remoteRef)
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-23 18:11:56 +02:00
|
|
|
"be flushed on shutdown" in {
|
|
|
|
|
val systemC = ActorSystem("systemC", system.settings.config)
|
|
|
|
|
try {
|
|
|
|
|
systemC.actorOf(TestActors.echoActorProps, "echo")
|
|
|
|
|
|
|
|
|
|
val addressC = RARP(systemC).provider.getDefaultAddress
|
|
|
|
|
val rootC = RootActorPath(addressC)
|
|
|
|
|
|
|
|
|
|
val remoteRef = {
|
|
|
|
|
system.actorSelection(rootC / "user" / "echo") ! Identify(None)
|
|
|
|
|
expectMsgType[ActorIdentity].ref.get
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
watch(remoteRef)
|
|
|
|
|
remoteRef ! "hello"
|
|
|
|
|
expectMsg("hello")
|
|
|
|
|
systemC.terminate()
|
|
|
|
|
// DeathWatchNotification is sent from systemC, failure detection takes longer than 3 seconds
|
|
|
|
|
expectTerminated(remoteRef, 5.seconds)
|
|
|
|
|
} finally {
|
|
|
|
|
shutdown(systemC)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-04 13:31:08 +02:00
|
|
|
"be resent when some in the middle are lost" in {
|
2016-05-09 07:31:41 +02:00
|
|
|
val replyProbe = TestProbe()
|
2016-05-12 08:56:28 +02:00
|
|
|
val controlSubject = new TestControlMessageSubject
|
|
|
|
|
val inboundContextB = new ManualReplyInboundContext(replyProbe.ref, addressB, controlSubject)
|
|
|
|
|
val inboundContextA = new TestInboundContext(addressB, controlSubject)
|
2016-05-09 07:31:41 +02:00
|
|
|
val outboundContextA = inboundContextA.association(addressB.address)
|
|
|
|
|
|
|
|
|
|
val sink = send(sendCount = 5, resendInterval = 60.seconds, outboundContextA)
|
|
|
|
|
.via(drop(dropSeqNumbers = Vector(3L, 4L)))
|
|
|
|
|
.via(inbound(inboundContextB))
|
2016-09-08 15:01:32 +02:00
|
|
|
.map(_.message.asInstanceOf[TestSysMsg])
|
2016-05-09 07:31:41 +02:00
|
|
|
.runWith(TestSink.probe)
|
2016-05-04 13:31:08 +02:00
|
|
|
|
|
|
|
|
sink.request(100)
|
2016-09-08 15:01:32 +02:00
|
|
|
sink.expectNext(TestSysMsg("msg-1"))
|
|
|
|
|
sink.expectNext(TestSysMsg("msg-2"))
|
2016-05-09 07:31:41 +02:00
|
|
|
replyProbe.expectMsg(Ack(1L, addressB))
|
|
|
|
|
replyProbe.expectMsg(Ack(2L, addressB))
|
2016-05-04 13:31:08 +02:00
|
|
|
// 3 and 4 was dropped
|
2016-05-09 07:31:41 +02:00
|
|
|
replyProbe.expectMsg(Nack(2L, addressB))
|
2016-05-04 13:31:08 +02:00
|
|
|
sink.expectNoMsg(100.millis) // 3 was dropped
|
2016-05-09 07:31:41 +02:00
|
|
|
inboundContextB.deliverLastReply()
|
2016-05-04 13:31:08 +02:00
|
|
|
// resending 3, 4, 5
|
2016-09-08 15:01:32 +02:00
|
|
|
sink.expectNext(TestSysMsg("msg-3"))
|
2016-05-09 07:31:41 +02:00
|
|
|
replyProbe.expectMsg(Ack(3L, addressB))
|
2016-09-08 15:01:32 +02:00
|
|
|
sink.expectNext(TestSysMsg("msg-4"))
|
2016-05-09 07:31:41 +02:00
|
|
|
replyProbe.expectMsg(Ack(4L, addressB))
|
2016-09-08 15:01:32 +02:00
|
|
|
sink.expectNext(TestSysMsg("msg-5"))
|
2016-05-09 07:31:41 +02:00
|
|
|
replyProbe.expectMsg(Ack(5L, addressB))
|
|
|
|
|
replyProbe.expectNoMsg(100.millis)
|
|
|
|
|
inboundContextB.deliverLastReply()
|
2016-05-04 13:31:08 +02:00
|
|
|
sink.expectComplete()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"be resent when first is lost" in {
|
2016-05-09 07:31:41 +02:00
|
|
|
val replyProbe = TestProbe()
|
2016-05-12 08:56:28 +02:00
|
|
|
val controlSubject = new TestControlMessageSubject
|
|
|
|
|
val inboundContextB = new ManualReplyInboundContext(replyProbe.ref, addressB, controlSubject)
|
|
|
|
|
val inboundContextA = new TestInboundContext(addressB, controlSubject)
|
2016-05-09 07:31:41 +02:00
|
|
|
val outboundContextA = inboundContextA.association(addressB.address)
|
|
|
|
|
|
|
|
|
|
val sink = send(sendCount = 3, resendInterval = 60.seconds, outboundContextA)
|
|
|
|
|
.via(drop(dropSeqNumbers = Vector(1L)))
|
|
|
|
|
.via(inbound(inboundContextB))
|
2016-09-08 15:01:32 +02:00
|
|
|
.map(_.message.asInstanceOf[TestSysMsg])
|
2016-05-09 07:31:41 +02:00
|
|
|
.runWith(TestSink.probe)
|
2016-05-04 13:31:08 +02:00
|
|
|
|
|
|
|
|
sink.request(100)
|
2016-05-09 07:31:41 +02:00
|
|
|
replyProbe.expectMsg(Nack(0L, addressB)) // from receiving 2
|
|
|
|
|
replyProbe.expectMsg(Nack(0L, addressB)) // from receiving 3
|
2016-05-04 13:31:08 +02:00
|
|
|
sink.expectNoMsg(100.millis) // 1 was dropped
|
2016-05-09 07:31:41 +02:00
|
|
|
inboundContextB.deliverLastReply() // it's ok to not delivery all nacks
|
2016-05-04 13:31:08 +02:00
|
|
|
// resending 1, 2, 3
|
2016-09-08 15:01:32 +02:00
|
|
|
sink.expectNext(TestSysMsg("msg-1"))
|
2016-05-09 07:31:41 +02:00
|
|
|
replyProbe.expectMsg(Ack(1L, addressB))
|
2016-09-08 15:01:32 +02:00
|
|
|
sink.expectNext(TestSysMsg("msg-2"))
|
2016-05-09 07:31:41 +02:00
|
|
|
replyProbe.expectMsg(Ack(2L, addressB))
|
2016-09-08 15:01:32 +02:00
|
|
|
sink.expectNext(TestSysMsg("msg-3"))
|
2016-05-09 07:31:41 +02:00
|
|
|
replyProbe.expectMsg(Ack(3L, addressB))
|
|
|
|
|
inboundContextB.deliverLastReply()
|
2016-05-04 13:31:08 +02:00
|
|
|
sink.expectComplete()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"be resent when last is lost" in {
|
2016-05-09 07:31:41 +02:00
|
|
|
val replyProbe = TestProbe()
|
2016-05-12 08:56:28 +02:00
|
|
|
val controlSubject = new TestControlMessageSubject
|
|
|
|
|
val inboundContextB = new ManualReplyInboundContext(replyProbe.ref, addressB, controlSubject)
|
|
|
|
|
val inboundContextA = new TestInboundContext(addressB, controlSubject)
|
2016-05-09 07:31:41 +02:00
|
|
|
val outboundContextA = inboundContextA.association(addressB.address)
|
|
|
|
|
|
2016-05-17 14:05:50 +02:00
|
|
|
val sink = send(sendCount = 3, resendInterval = 2.seconds, outboundContextA)
|
2016-05-09 07:31:41 +02:00
|
|
|
.via(drop(dropSeqNumbers = Vector(3L)))
|
|
|
|
|
.via(inbound(inboundContextB))
|
2016-09-08 15:01:32 +02:00
|
|
|
.map(_.message.asInstanceOf[TestSysMsg])
|
2016-05-09 07:31:41 +02:00
|
|
|
.runWith(TestSink.probe)
|
2016-05-04 13:31:08 +02:00
|
|
|
|
|
|
|
|
sink.request(100)
|
2016-09-08 15:01:32 +02:00
|
|
|
sink.expectNext(TestSysMsg("msg-1"))
|
2016-05-09 07:31:41 +02:00
|
|
|
replyProbe.expectMsg(Ack(1L, addressB))
|
|
|
|
|
inboundContextB.deliverLastReply()
|
2016-09-08 15:01:32 +02:00
|
|
|
sink.expectNext(TestSysMsg("msg-2"))
|
2016-05-09 07:31:41 +02:00
|
|
|
replyProbe.expectMsg(Ack(2L, addressB))
|
|
|
|
|
inboundContextB.deliverLastReply()
|
2016-05-04 13:31:08 +02:00
|
|
|
sink.expectNoMsg(200.millis) // 3 was dropped
|
|
|
|
|
// resending 3 due to timeout
|
2016-09-08 15:01:32 +02:00
|
|
|
sink.expectNext(TestSysMsg("msg-3"))
|
2016-05-17 14:05:50 +02:00
|
|
|
replyProbe.expectMsg(4.seconds, Ack(3L, addressB))
|
|
|
|
|
// continue resending
|
|
|
|
|
replyProbe.expectMsg(4.seconds, Ack(3L, addressB))
|
2016-05-09 07:31:41 +02:00
|
|
|
inboundContextB.deliverLastReply()
|
2016-05-17 14:05:50 +02:00
|
|
|
replyProbe.expectNoMsg(2200.millis)
|
2016-05-04 13:31:08 +02:00
|
|
|
sink.expectComplete()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"deliver all during stress and random dropping" in {
|
|
|
|
|
val N = 10000
|
|
|
|
|
val dropRate = 0.1
|
2016-05-12 08:56:28 +02:00
|
|
|
val controlSubject = new TestControlMessageSubject
|
|
|
|
|
val inboundContextB = new TestInboundContext(addressB, controlSubject, replyDropRate = dropRate)
|
|
|
|
|
val inboundContextA = new TestInboundContext(addressB, controlSubject)
|
2016-05-09 07:31:41 +02:00
|
|
|
val outboundContextA = inboundContextA.association(addressB.address)
|
2016-05-04 13:31:08 +02:00
|
|
|
|
|
|
|
|
val output =
|
2016-05-09 07:31:41 +02:00
|
|
|
send(N, 1.second, outboundContextA)
|
2016-05-04 13:31:08 +02:00
|
|
|
.via(randomDrop(dropRate))
|
2016-05-09 07:31:41 +02:00
|
|
|
.via(inbound(inboundContextB))
|
2016-09-08 15:01:32 +02:00
|
|
|
.map(_.message.asInstanceOf[TestSysMsg])
|
2016-05-04 13:31:08 +02:00
|
|
|
.runWith(Sink.seq)
|
|
|
|
|
|
2016-09-08 15:01:32 +02:00
|
|
|
Await.result(output, 20.seconds) should ===((1 to N).map(n ⇒ TestSysMsg("msg-" + n)).toVector)
|
2016-05-04 13:31:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"deliver all during throttling and random dropping" in {
|
|
|
|
|
val N = 500
|
|
|
|
|
val dropRate = 0.1
|
2016-05-12 08:56:28 +02:00
|
|
|
val controlSubject = new TestControlMessageSubject
|
|
|
|
|
val inboundContextB = new TestInboundContext(addressB, controlSubject, replyDropRate = dropRate)
|
|
|
|
|
val inboundContextA = new TestInboundContext(addressB, controlSubject)
|
2016-05-09 07:31:41 +02:00
|
|
|
val outboundContextA = inboundContextA.association(addressB.address)
|
2016-05-04 13:31:08 +02:00
|
|
|
|
|
|
|
|
val output =
|
2016-05-09 07:31:41 +02:00
|
|
|
send(N, 1.second, outboundContextA)
|
2016-05-04 13:31:08 +02:00
|
|
|
.throttle(200, 1.second, 10, ThrottleMode.shaping)
|
|
|
|
|
.via(randomDrop(dropRate))
|
2016-05-09 07:31:41 +02:00
|
|
|
.via(inbound(inboundContextB))
|
2016-09-08 15:01:32 +02:00
|
|
|
.map(_.message.asInstanceOf[TestSysMsg])
|
2016-05-04 13:31:08 +02:00
|
|
|
.runWith(Sink.seq)
|
|
|
|
|
|
2016-09-08 15:01:32 +02:00
|
|
|
Await.result(output, 20.seconds) should ===((1 to N).map(n ⇒ TestSysMsg("msg-" + n)).toVector)
|
2016-05-04 13:31:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|