* Since DeathWatchNotification is sent over the control channel it may overtake
other messages that have been sent from the same actor before it stopped.
* It can be confusing that Terminated can't be used as an end-of-conversation marker.
* In classic Remoting we didn't have this problem because all messages were sent over
the same connection.
* don't send DeathWatchNotification when system is terminating
* when using Cluster we can rely on that the other side will publish AddressTerminated
when the member has been removed
* it's actually already a race condition that often will result in that the DeathWatchNotification
from the terminating side
* in DeathWatch.scala it will remove the watchedBy when receiving AddressTerminated, and that
may (sometimes) happen before tellWatchersWeDied
* same for Unwatch
* to avoid sending many Unwatch messages when watcher's ActorSystem is terminated
* same race exists for Unwatch as for DeathWatchNotification, if RemoteWatcher publishAddressTerminated
before the watcher is terminated
* config for the flush timeout, and possibility to disable
168 lines
5.7 KiB
Scala
168 lines
5.7 KiB
Scala
/*
|
|
* Copyright (C) 2009-2020 Lightbend Inc. <https://www.lightbend.com>
|
|
*/
|
|
|
|
package akka.cluster
|
|
|
|
import scala.concurrent.duration._
|
|
|
|
import akka.actor._
|
|
import akka.testkit._
|
|
import com.typesafe.config.ConfigFactory
|
|
|
|
import akka.remote.artery.ArteryMultiNodeSpec
|
|
import akka.remote.artery.ArterySpecSupport
|
|
|
|
object ClusterDeathWatchNotificationSpec {
|
|
|
|
val config = ConfigFactory.parseString(s"""
|
|
akka {
|
|
loglevel = INFO
|
|
actor {
|
|
provider = cluster
|
|
}
|
|
}
|
|
akka.remote.classic.netty.tcp.port = 0
|
|
akka.remote.artery.canonical.port = 0
|
|
""").withFallback(ArterySpecSupport.defaultConfig)
|
|
|
|
object Sender {
|
|
def props(receiver: ActorRef, sendOnStop: Vector[String]): Props =
|
|
Props(new Sender(receiver, sendOnStop))
|
|
}
|
|
|
|
class Sender(receiver: ActorRef, sendOnStop: Vector[String]) extends Actor {
|
|
override def receive: Receive = {
|
|
case msg => sender() ! msg
|
|
}
|
|
|
|
override def postStop(): Unit = {
|
|
sendOnStop.foreach(receiver ! _)
|
|
}
|
|
}
|
|
}
|
|
|
|
class ClusterDeathWatchNotificationSpec
|
|
extends ArteryMultiNodeSpec(ClusterDeathWatchNotificationSpec.config)
|
|
with ImplicitSender {
|
|
import ClusterDeathWatchNotificationSpec.Sender
|
|
|
|
private def system1: ActorSystem = system
|
|
private val system2 = newRemoteSystem(name = Some(system.name))
|
|
private val system3 = newRemoteSystem(name = Some(system.name))
|
|
private val systems = Vector(system1, system2, system3)
|
|
|
|
private val messages = (1 to 100).map(_.toString).toVector
|
|
|
|
private def setupSender(sys: ActorSystem, receiverProbe: TestProbe, name: String): Unit = {
|
|
val receiverPath = receiverProbe.ref.path.toStringWithAddress(address(system1))
|
|
val otherProbe = TestProbe()(sys)
|
|
sys.actorSelection(receiverPath).tell(Identify(None), otherProbe.ref)
|
|
val receiver = otherProbe.expectMsgType[ActorIdentity](5.seconds).ref.get
|
|
receiver.path.address.hasGlobalScope should ===(true) // should be remote
|
|
sys.actorOf(Sender.props(receiver, messages), name)
|
|
}
|
|
|
|
private def identifySender(sys: ActorSystem, name: String): ActorRef = {
|
|
system1.actorSelection(rootActorPath(sys) / "user" / name) ! Identify(None)
|
|
val sender = expectMsgType[ActorIdentity](5.seconds).ref.get
|
|
sender
|
|
}
|
|
|
|
"join cluster" in within(10.seconds) {
|
|
systems.foreach { sys =>
|
|
Cluster(sys).join(Cluster(system1).selfAddress)
|
|
}
|
|
awaitAssert {
|
|
systems.foreach { sys =>
|
|
Cluster(sys).state.members.size should ===(systems.size)
|
|
Cluster(sys).state.members.iterator.map(_.status).toSet should ===(Set(MemberStatus.Up))
|
|
}
|
|
}
|
|
}
|
|
|
|
"receive Terminated after ordinary messages" in {
|
|
val receiverProbe = TestProbe()
|
|
setupSender(system2, receiverProbe, "sender")
|
|
val sender = identifySender(system2, "sender")
|
|
|
|
receiverProbe.watch(sender)
|
|
// make it likely that the watch has been established
|
|
sender.tell("echo", receiverProbe.ref)
|
|
receiverProbe.expectMsg("echo")
|
|
|
|
sender ! PoisonPill
|
|
receiverProbe.receiveN(messages.size).toVector shouldBe messages
|
|
receiverProbe.expectTerminated(sender)
|
|
}
|
|
|
|
"receive Terminated after ordinary messages when system is shutdown" in {
|
|
val receiverProbe1 = TestProbe()
|
|
setupSender(system2, receiverProbe1, "sender1")
|
|
val sender1 = identifySender(system2, "sender1")
|
|
|
|
val receiverProbe2 = TestProbe()
|
|
setupSender(system2, receiverProbe2, "sender2")
|
|
val sender2 = identifySender(system2, "sender2")
|
|
|
|
val receiverProbe3 = TestProbe()
|
|
setupSender(system2, receiverProbe3, "sender3")
|
|
val sender3 = identifySender(system2, "sender3")
|
|
|
|
receiverProbe1.watch(sender1)
|
|
receiverProbe2.watch(sender2)
|
|
receiverProbe3.watch(sender3)
|
|
// make it likely that the watch has been established
|
|
sender1.tell("echo1", receiverProbe1.ref)
|
|
receiverProbe1.expectMsg("echo1")
|
|
sender2.tell("echo2", receiverProbe2.ref)
|
|
receiverProbe2.expectMsg("echo2")
|
|
sender3.tell("echo3", receiverProbe3.ref)
|
|
receiverProbe3.expectMsg("echo3")
|
|
|
|
system2.log.debug("terminating")
|
|
system2.terminate()
|
|
receiverProbe1.receiveN(messages.size, 5.seconds).toVector shouldBe messages
|
|
receiverProbe1.expectTerminated(sender1)
|
|
receiverProbe2.receiveN(messages.size).toVector shouldBe messages
|
|
receiverProbe2.expectTerminated(sender2)
|
|
receiverProbe3.receiveN(messages.size).toVector shouldBe messages
|
|
receiverProbe3.expectTerminated(sender3)
|
|
}
|
|
|
|
"receive Terminated after ordinary messages when system is leaving" in {
|
|
val receiverProbe1 = TestProbe()
|
|
setupSender(system3, receiverProbe1, "sender1")
|
|
val sender1 = identifySender(system3, "sender1")
|
|
|
|
val receiverProbe2 = TestProbe()
|
|
setupSender(system3, receiverProbe2, "sender2")
|
|
val sender2 = identifySender(system3, "sender2")
|
|
|
|
val receiverProbe3 = TestProbe()
|
|
setupSender(system3, receiverProbe3, "sender3")
|
|
val sender3 = identifySender(system3, "sender3")
|
|
|
|
receiverProbe1.watch(sender1)
|
|
receiverProbe2.watch(sender2)
|
|
receiverProbe3.watch(sender3)
|
|
// make it likely that the watch has been established
|
|
sender1.tell("echo1", receiverProbe1.ref)
|
|
receiverProbe1.expectMsg("echo1")
|
|
sender2.tell("echo2", receiverProbe2.ref)
|
|
receiverProbe2.expectMsg("echo2")
|
|
sender3.tell("echo3", receiverProbe3.ref)
|
|
receiverProbe3.expectMsg("echo3")
|
|
|
|
system3.log.debug("leaving")
|
|
Cluster(system1).leave(Cluster(system3).selfAddress)
|
|
|
|
receiverProbe1.receiveN(messages.size, 5.seconds).toVector shouldBe messages
|
|
receiverProbe1.expectTerminated(sender1)
|
|
receiverProbe2.receiveN(messages.size).toVector shouldBe messages
|
|
receiverProbe2.expectTerminated(sender2)
|
|
receiverProbe3.receiveN(messages.size).toVector shouldBe messages
|
|
receiverProbe3.expectTerminated(sender3)
|
|
}
|
|
|
|
}
|