pekko/akka-cluster/src/test/scala/akka/cluster/ClusterDeathWatchNotificationSpec.scala
Patrik Nordwall 8e2073a6a1 Flush messages before DeathWatchNotification, #28695 (#28940)
* 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
2020-09-25 14:37:47 +01:00

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)
}
}