2018-10-29 17:19:37 +08:00
/*
2019-01-02 18:55:26 +08:00
* Copyright ( C ) 2015 - 2019 Lightbend Inc . < https : //www.lightbend.com>
2015-06-01 19:03:00 +02:00
*/
2015-08-12 14:56:28 +02:00
package akka.pattern
2015-07-07 16:28:17 +02:00
2015-06-01 19:03:00 +02:00
import akka.actor._
import akka.testkit._
2018-09-14 16:52:52 +04:30
import org.scalatest.concurrent.Eventually
2018-01-29 12:05:28 +01:00
import org.scalatest.prop.TableDrivenPropertyChecks._
import scala.concurrent.duration._
2015-11-16 17:42:24 +01:00
import scala.util.control.NoStackTrace
2015-06-01 19:03:00 +02:00
object BackoffSupervisorSpec {
2015-11-16 17:42:24 +01:00
class TestException extends RuntimeException with NoStackTrace
2015-06-01 19:03:00 +02:00
object Child {
def props ( probe : ActorRef ) : Props =
Props ( new Child ( probe ) )
}
class Child ( probe : ActorRef ) extends Actor {
def receive = {
2015-11-16 17:42:24 +01:00
case "boom" ⇒ throw new TestException
case msg ⇒ probe ! msg
2015-06-01 19:03:00 +02:00
}
}
2015-12-23 20:18:19 +02:00
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
}
}
2015-06-01 19:03:00 +02:00
}
2018-09-14 16:52:52 +04:30
class BackoffSupervisorSpec extends AkkaSpec with ImplicitSender with Eventually {
2015-06-01 19:03:00 +02:00
import BackoffSupervisorSpec._
2018-09-14 16:52:52 +04:30
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 )
2015-12-23 20:18:19 +02:00
def create ( options : BackoffOptions ) = system . actorOf ( BackoffSupervisor . props ( options ) )
2015-06-01 19:03:00 +02:00
"BackoffSupervisor" must {
2015-12-23 20:18:19 +02:00
"start child again when it stops when using `Backoff.onStop`" in {
val supervisor = create ( onStopOptions ( ) )
2015-06-01 19:03:00 +02:00
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 {
2015-12-23 20:18:19 +02:00
def assertForward ( supervisor : ActorRef ) = {
supervisor ! "hello"
expectMsg ( "hello" )
}
assertForward ( create ( onStopOptions ( ) ) )
assertForward ( create ( onFailureOptions ( ) ) )
2015-06-01 19:03:00 +02:00
}
2015-11-16 17:42:24 +01:00
2015-12-23 20:18:19 +02:00
"support custom supervision strategy" in {
def assertCustomStrategy ( supervisor : ActorRef ) = {
2015-11-16 17:42:24 +01:00
supervisor ! BackoffSupervisor . GetCurrentChild
2015-12-23 20:18:19 +02:00
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 )
2016-01-22 14:24:36 +01:00
awaitAssert {
supervisor ! BackoffSupervisor . GetRestartCount
expectMsg ( BackoffSupervisor . RestartCount ( 1 ) )
}
2015-12-23 20:18:19 +02:00
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 ) ) )
2015-11-16 17:42:24 +01:00
}
}
2016-09-05 23:47:58 -04:00
2016-12-02 03:07:33 +09:00
"reply to sender if replyWhileStopped is specified" in {
filterException [ TestException ] {
2018-09-14 16:52:52 +04:30
val supervisor = create ( Backoff . onFailure ( Child . props ( testActor ) , "c1" , 100. seconds , 300. seconds , 0.2 , maxNrOfRetries = - 1 ) . withReplyWhileStopped ( "child was stopped" ) )
2016-12-02 03:07:33 +09:00
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 ] {
2018-09-14 16:52:52 +04:30
val supervisor = create ( Backoff . onFailure ( Child . props ( testActor ) , "c1" , 100. seconds , 300. seconds , 0.2 , maxNrOfRetries = - 1 ) )
2016-12-02 03:07:33 +09:00
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 )
}
2016-09-05 23:47:58 -04:00
}
2018-01-29 12:05:28 +01:00
"correctly calculate the delay" in {
val delayTable =
Table (
( "restartCount" , "minBackoff" , "maxBackoff" , "randomFactor" , "expectedResult" ) ,
( 0 , 0. minutes , 0. minutes , 0 d , 0. minutes ) ,
( 0 , 5. minutes , 7. minutes , 0 d , 5. minutes ) ,
( 2 , 5. seconds , 7. seconds , 0 d , 7. seconds ) ,
( 2 , 5. seconds , 7.d ays , 0 d , 20. seconds ) ,
( 29 , 5. minutes , 10. minutes , 0 d , 10. minutes ) ,
( 29 , 10000.d ays , 10000.d ays , 0 d , 10000.d ays ) ,
( Int . MaxValue , 10000.d ays , 10000.d ays , 0 d , 10000.d ays ) )
forAll ( delayTable ) { (
restartCount : Int ,
minBackoff : FiniteDuration ,
maxBackoff : FiniteDuration ,
randomFactor : Double ,
expectedResult : FiniteDuration ) ⇒
val calculatedValue = BackoffSupervisor . calculateDelay ( restartCount , minBackoff , maxBackoff , randomFactor )
assert ( calculatedValue === expectedResult )
}
}
2018-09-14 16:52:52 +04:30
"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"
2018-12-05 10:25:50 +00:00
withClue ( "Expected child and supervisor to terminate" ) {
Set ( expectMsgType [ Terminated ] . actor , expectMsgType [ Terminated ] . actor ) shouldEqual Set ( c3 , supervisor )
}
2018-09-14 16:52:52 +04:30
}
}
2018-12-06 10:10:37 +00:00
"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 )
2018-12-20 07:38:24 +01:00
}
2018-12-06 10:10:37 +00:00
2018-12-20 07:38:24 +01:00
"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 )
2018-12-06 10:10:37 +00:00
}
2015-06-01 19:03:00 +02:00
}
}