pekko/akka-actor-tests/src/test/scala/akka/pattern/BackoffSupervisorSpec.scala
Patrik Nordwall 5c96a5f556 replace unicode arrows
* ⇒, →, ←
* because we don't want to show them in documentation snippets and
  then it's complicated to avoid that when snippets are
  located in src/test/scala in individual modules
* dont replace object `→` in FSM.scala and PersistentFSM.scala
2019-03-11 16:58:51 +01:00

374 lines
12 KiB
Scala

/*
* Copyright (C) 2015-2019 Lightbend Inc. <https://www.lightbend.com>
*/
package akka.pattern
import akka.actor._
import akka.testkit._
import org.scalatest.concurrent.Eventually
import org.scalatest.prop.TableDrivenPropertyChecks._
import scala.concurrent.duration._
import scala.util.control.NoStackTrace
object BackoffSupervisorSpec {
class TestException extends RuntimeException with NoStackTrace
object Child {
def props(probe: ActorRef): Props =
Props(new Child(probe))
}
class Child(probe: ActorRef) extends Actor {
def receive = {
case "boom" => throw new TestException
case msg => probe ! msg
}
}
object ManualChild {
def props(probe: ActorRef): Props =
Props(new ManualChild(probe))
}
class ManualChild(probe: ActorRef) extends Actor {
def receive = {
case "boom" => throw new TestException
case msg =>
probe ! msg
context.parent ! BackoffSupervisor.Reset
}
}
}
class BackoffSupervisorSpec extends AkkaSpec with ImplicitSender with Eventually {
import BackoffSupervisorSpec._
def onStopOptions(props: Props = Child.props(testActor), maxNrOfRetries: Int = -1) = Backoff.onStop(props, "c1", 100.millis, 3.seconds, 0.2, maxNrOfRetries)
def onFailureOptions(props: Props = Child.props(testActor), maxNrOfRetries: Int = -1) = Backoff.onFailure(props, "c1", 100.millis, 3.seconds, 0.2, maxNrOfRetries)
def create(options: BackoffOptions) = system.actorOf(BackoffSupervisor.props(options))
"BackoffSupervisor" must {
"start child again when it stops when using `Backoff.onStop`" in {
val supervisor = create(onStopOptions())
supervisor ! BackoffSupervisor.GetCurrentChild
val c1 = expectMsgType[BackoffSupervisor.CurrentChild].ref.get
watch(c1)
c1 ! PoisonPill
expectTerminated(c1)
awaitAssert {
supervisor ! BackoffSupervisor.GetCurrentChild
// new instance
expectMsgType[BackoffSupervisor.CurrentChild].ref.get should !==(c1)
}
}
"forward messages to the child" in {
def assertForward(supervisor: ActorRef) = {
supervisor ! "hello"
expectMsg("hello")
}
assertForward(create(onStopOptions()))
assertForward(create(onFailureOptions()))
}
"support custom supervision strategy" in {
def assertCustomStrategy(supervisor: ActorRef) = {
supervisor ! BackoffSupervisor.GetCurrentChild
val c1 = expectMsgType[BackoffSupervisor.CurrentChild].ref.get
watch(c1)
c1 ! "boom"
expectTerminated(c1)
awaitAssert {
supervisor ! BackoffSupervisor.GetCurrentChild
// new instance
expectMsgType[BackoffSupervisor.CurrentChild].ref.get should !==(c1)
}
}
filterException[TestException] {
val stoppingStrategy = OneForOneStrategy() {
case _: TestException => SupervisorStrategy.Stop
}
val restartingStrategy = OneForOneStrategy() {
case _: TestException => SupervisorStrategy.Restart
}
assertCustomStrategy(
create(onStopOptions()
.withSupervisorStrategy(stoppingStrategy)))
assertCustomStrategy(
create(onFailureOptions()
.withSupervisorStrategy(restartingStrategy)))
}
}
"support default stopping strategy when using `Backoff.onStop`" in {
filterException[TestException] {
val supervisor = create(onStopOptions().withDefaultStoppingStrategy)
supervisor ! BackoffSupervisor.GetCurrentChild
val c1 = expectMsgType[BackoffSupervisor.CurrentChild].ref.get
watch(c1)
supervisor ! BackoffSupervisor.GetRestartCount
expectMsg(BackoffSupervisor.RestartCount(0))
c1 ! "boom"
expectTerminated(c1)
awaitAssert {
supervisor ! BackoffSupervisor.GetCurrentChild
// new instance
expectMsgType[BackoffSupervisor.CurrentChild].ref.get should !==(c1)
}
supervisor ! BackoffSupervisor.GetRestartCount
expectMsg(BackoffSupervisor.RestartCount(1))
}
}
"support manual reset" in {
filterException[TestException] {
def assertManualReset(supervisor: ActorRef) = {
supervisor ! BackoffSupervisor.GetCurrentChild
val c1 = expectMsgType[BackoffSupervisor.CurrentChild].ref.get
watch(c1)
c1 ! "boom"
expectTerminated(c1)
awaitAssert {
supervisor ! BackoffSupervisor.GetRestartCount
expectMsg(BackoffSupervisor.RestartCount(1))
}
awaitAssert {
supervisor ! BackoffSupervisor.GetCurrentChild
// new instance
expectMsgType[BackoffSupervisor.CurrentChild].ref.get should !==(c1)
}
supervisor ! "hello"
expectMsg("hello")
// making sure the Reset is handled by supervisor
supervisor ! "hello"
expectMsg("hello")
supervisor ! BackoffSupervisor.GetRestartCount
expectMsg(BackoffSupervisor.RestartCount(0))
}
val stoppingStrategy = OneForOneStrategy() {
case _: TestException => SupervisorStrategy.Stop
}
val restartingStrategy = OneForOneStrategy() {
case _: TestException => SupervisorStrategy.Restart
}
assertManualReset(
create(onStopOptions(ManualChild.props(testActor))
.withManualReset
.withSupervisorStrategy(stoppingStrategy)))
assertManualReset(
create(onFailureOptions(ManualChild.props(testActor))
.withManualReset
.withSupervisorStrategy(restartingStrategy)))
}
}
"reply to sender if replyWhileStopped is specified" in {
filterException[TestException] {
val supervisor = create(Backoff.onFailure(Child.props(testActor), "c1", 100.seconds, 300.seconds, 0.2, maxNrOfRetries = -1).withReplyWhileStopped("child was stopped"))
supervisor ! BackoffSupervisor.GetCurrentChild
val c1 = expectMsgType[BackoffSupervisor.CurrentChild].ref.get
watch(c1)
supervisor ! BackoffSupervisor.GetRestartCount
expectMsg(BackoffSupervisor.RestartCount(0))
c1 ! "boom"
expectTerminated(c1)
awaitAssert {
supervisor ! BackoffSupervisor.GetRestartCount
expectMsg(BackoffSupervisor.RestartCount(1))
}
supervisor ! "boom"
expectMsg("child was stopped")
}
}
"not reply to sender if replyWhileStopped is NOT specified" in {
filterException[TestException] {
val supervisor = create(Backoff.onFailure(Child.props(testActor), "c1", 100.seconds, 300.seconds, 0.2, maxNrOfRetries = -1))
supervisor ! BackoffSupervisor.GetCurrentChild
val c1 = expectMsgType[BackoffSupervisor.CurrentChild].ref.get
watch(c1)
supervisor ! BackoffSupervisor.GetRestartCount
expectMsg(BackoffSupervisor.RestartCount(0))
c1 ! "boom"
expectTerminated(c1)
awaitAssert {
supervisor ! BackoffSupervisor.GetRestartCount
expectMsg(BackoffSupervisor.RestartCount(1))
}
supervisor ! "boom" //this will be sent to deadLetters
expectNoMsg(500.milliseconds)
}
}
"correctly calculate the delay" in {
val delayTable =
Table(
("restartCount", "minBackoff", "maxBackoff", "randomFactor", "expectedResult"),
(0, 0.minutes, 0.minutes, 0d, 0.minutes),
(0, 5.minutes, 7.minutes, 0d, 5.minutes),
(2, 5.seconds, 7.seconds, 0d, 7.seconds),
(2, 5.seconds, 7.days, 0d, 20.seconds),
(29, 5.minutes, 10.minutes, 0d, 10.minutes),
(29, 10000.days, 10000.days, 0d, 10000.days),
(Int.MaxValue, 10000.days, 10000.days, 0d, 10000.days))
forAll(delayTable) { (
restartCount: Int,
minBackoff: FiniteDuration,
maxBackoff: FiniteDuration,
randomFactor: Double,
expectedResult: FiniteDuration) =>
val calculatedValue = BackoffSupervisor.calculateDelay(restartCount, minBackoff, maxBackoff, randomFactor)
assert(calculatedValue === expectedResult)
}
}
"stop restarting the child after reaching maxNrOfRetries limit (Backoff.onStop)" in {
val supervisor = create(onStopOptions(maxNrOfRetries = 2))
def waitForChild: Option[ActorRef] = {
eventually(timeout(1.second), interval(50.millis)) {
supervisor ! BackoffSupervisor.GetCurrentChild
val c = expectMsgType[BackoffSupervisor.CurrentChild].ref
c.isDefined shouldBe true
}
supervisor ! BackoffSupervisor.GetCurrentChild
expectMsgType[BackoffSupervisor.CurrentChild].ref
}
watch(supervisor)
supervisor ! BackoffSupervisor.GetRestartCount
expectMsg(BackoffSupervisor.RestartCount(0))
supervisor ! BackoffSupervisor.GetCurrentChild
val c1 = expectMsgType[BackoffSupervisor.CurrentChild].ref.get
watch(c1)
c1 ! PoisonPill
expectTerminated(c1)
supervisor ! BackoffSupervisor.GetRestartCount
expectMsg(BackoffSupervisor.RestartCount(1))
val c2 = waitForChild.get
awaitAssert(c2 should !==(c1))
watch(c2)
c2 ! PoisonPill
expectTerminated(c2)
supervisor ! BackoffSupervisor.GetRestartCount
expectMsg(BackoffSupervisor.RestartCount(2))
val c3 = waitForChild.get
awaitAssert(c3 should !==(c2))
watch(c3)
c3 ! PoisonPill
expectTerminated(c3)
expectTerminated(supervisor)
}
"stop restarting the child after reaching maxNrOfRetries limit (Backoff.onFailure)" in {
filterException[TestException] {
val supervisor = create(onFailureOptions(maxNrOfRetries = 2))
def waitForChild: Option[ActorRef] = {
eventually(timeout(1.second), interval(50.millis)) {
supervisor ! BackoffSupervisor.GetCurrentChild
val c = expectMsgType[BackoffSupervisor.CurrentChild].ref
c.isDefined shouldBe true
}
supervisor ! BackoffSupervisor.GetCurrentChild
expectMsgType[BackoffSupervisor.CurrentChild].ref
}
watch(supervisor)
supervisor ! BackoffSupervisor.GetRestartCount
expectMsg(BackoffSupervisor.RestartCount(0))
supervisor ! BackoffSupervisor.GetCurrentChild
val c1 = expectMsgType[BackoffSupervisor.CurrentChild].ref.get
watch(c1)
c1 ! "boom"
expectTerminated(c1)
supervisor ! BackoffSupervisor.GetRestartCount
expectMsg(BackoffSupervisor.RestartCount(1))
val c2 = waitForChild.get
awaitAssert(c2 should !==(c1))
watch(c2)
c2 ! "boom"
expectTerminated(c2)
supervisor ! BackoffSupervisor.GetRestartCount
expectMsg(BackoffSupervisor.RestartCount(2))
val c3 = waitForChild.get
awaitAssert(c3 should !==(c2))
watch(c3)
c3 ! "boom"
withClue("Expected child and supervisor to terminate") {
Set(expectMsgType[Terminated].actor, expectMsgType[Terminated].actor) shouldEqual Set(c3, supervisor)
}
}
}
"stop restarting the child if final stop message received (Backoff.onStop)" in {
val stopMessage = "stop"
val supervisor: ActorRef = create(onStopOptions(maxNrOfRetries = 100).withFinalStopMessage(_ == stopMessage))
supervisor ! BackoffSupervisor.GetCurrentChild
val c1 = expectMsgType[BackoffSupervisor.CurrentChild].ref.get
watch(c1)
watch(supervisor)
supervisor ! stopMessage
expectMsg("stop")
c1 ! PoisonPill
expectTerminated(c1)
expectTerminated(supervisor)
}
"supervisor must not stop when final stop message has not been received" in {
val stopMessage = "stop"
val supervisorWatcher = TestProbe()
val supervisor: ActorRef = create(onStopOptions(maxNrOfRetries = 100).withFinalStopMessage(_ == stopMessage))
supervisor ! BackoffSupervisor.GetCurrentChild
val c1 = expectMsgType[BackoffSupervisor.CurrentChild].ref.get
watch(c1)
watch(supervisor)
supervisorWatcher.watch(supervisor)
c1 ! PoisonPill
expectTerminated(c1)
supervisor ! "ping"
supervisorWatcher.expectNoMessage(20.millis) // supervisor must not terminate
supervisor ! stopMessage
expectTerminated(supervisor)
}
}
}