diff --git a/akka-actor-tests/src/test/scala/akka/pattern/BackoffSupervisorSpec.scala b/akka-actor-tests/src/test/scala/akka/pattern/BackoffSupervisorSpec.scala index 814ae09b58..bc72785fa7 100644 --- a/akka-actor-tests/src/test/scala/akka/pattern/BackoffSupervisorSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/pattern/BackoffSupervisorSpec.scala @@ -7,8 +7,13 @@ package akka.pattern import scala.concurrent.duration._ import akka.actor._ import akka.testkit._ +import scala.util.control.NoStackTrace +import akka.actor.SupervisorStrategy object BackoffSupervisorSpec { + + class TestException extends RuntimeException with NoStackTrace + object Child { def props(probe: ActorRef): Props = Props(new Child(probe)) @@ -16,7 +21,8 @@ object BackoffSupervisorSpec { class Child(probe: ActorRef) extends Actor { def receive = { - case msg ⇒ probe ! msg + case "boom" ⇒ throw new TestException + case msg ⇒ probe ! msg } } } @@ -46,5 +52,23 @@ class BackoffSupervisorSpec extends AkkaSpec with ImplicitSender { supervisor ! "hello" expectMsg("hello") } + + "support custom supervision decider" in { + val supervisor = system.actorOf( + BackoffSupervisor.propsWithSupervisorStrategy(Child.props(testActor), "c1", 100.millis, 3.seconds, 0.2, + OneForOneStrategy() { + case _: TestException ⇒ SupervisorStrategy.Stop + })) + 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) + } + } } } diff --git a/akka-actor/src/main/scala/akka/pattern/BackoffSupervisor.scala b/akka-actor/src/main/scala/akka/pattern/BackoffSupervisor.scala index 40579d4b1a..9bf86f7af1 100644 --- a/akka-actor/src/main/scala/akka/pattern/BackoffSupervisor.scala +++ b/akka-actor/src/main/scala/akka/pattern/BackoffSupervisor.scala @@ -12,12 +12,19 @@ import akka.actor.Props import akka.actor.Terminated import java.util.Optional import scala.concurrent.duration.Duration +import akka.actor.SupervisorStrategy.Decider +import akka.actor.OneForOneStrategy +import akka.actor.SupervisorStrategy object BackoffSupervisor { /** * Props for creating an [[BackoffSupervisor]] actor. * + * Exceptions in the child are handled with the default supervision strategy, i.e. + * most exceptions will immediately restart the child. You can define another + * supervision strategy by using [[#propsWithSupervisorStrategy]]. + * * @param childProps the [[akka.actor.Props]] of the child actor that * will be started and supervised * @param childName name of the child actor @@ -34,10 +41,40 @@ object BackoffSupervisor { minBackoff: FiniteDuration, maxBackoff: FiniteDuration, randomFactor: Double): Props = { + propsWithSupervisorStrategy(childProps, childName, minBackoff, maxBackoff, randomFactor, SupervisorStrategy.defaultStrategy) + } + + /** + * Props for creating an [[BackoffSupervisor]] actor with a custom + * supervision strategy. + * + * Exceptions in the child are handled with the given `supervisionStrategy`. A + * `Restart` will perform a normal immediate restart of the child. A `Stop` will + * stop the child, but it will be started again after the back-off duration. + * + * @param childProps the [[akka.actor.Props]] of the child actor that + * will be started and supervised + * @param childName name of the child actor + * @param minBackoff minimum (initial) duration until the child actor will + * started again, if it is terminated + * @param maxBackoff the exponential back-off is capped to this duration + * @param randomFactor after calculation of the exponential back-off an additional + * random delay based on this factor is added, e.g. `0.2` adds up to `20%` delay. + * In order to skip this additional delay pass in `0`. + * @param strategy the supervision strategy to use for handling exceptions + * in the child + */ + def propsWithSupervisorStrategy( + childProps: Props, + childName: String, + minBackoff: FiniteDuration, + maxBackoff: FiniteDuration, + randomFactor: Double, + strategy: SupervisorStrategy): Props = { require(minBackoff > Duration.Zero, "minBackoff must be > 0") require(maxBackoff >= minBackoff, "maxBackoff must be >= minBackoff") require(0.0 <= randomFactor && randomFactor <= 1.0, "randomFactor must be between 0.0 and 1.0") - Props(new BackoffSupervisor(childProps, childName, minBackoff, maxBackoff, randomFactor)) + Props(new BackoffSupervisor(childProps, childName, minBackoff, maxBackoff, randomFactor, strategy)) } /** @@ -111,17 +148,25 @@ object BackoffSupervisor { * message to this actor and it will reply with [[akka.pattern.BackoffSupervisor.CurrentChild]] * containing the `ActorRef` of the current child, if any. * + * The `BackoffSupervisor` forwards all messages from the child to the parent of the + * `BackoffSupervisor`. + * * The `BackoffSupervisor` forwards all other messages to the child, if it is currently running. * * The child can stop itself and send a [[akka.actor.PoisonPill]] to the parent supervisor * if it wants to do an intentional stop. + * + * Exceptions in the child are handled with the given `supervisionStrategy`. A + * `Restart` will perform a normal immediate restart of the child. A `Stop` will + * stop the child, but it will be started again after the back-off duration. */ final class BackoffSupervisor( childProps: Props, childName: String, minBackoff: FiniteDuration, maxBackoff: FiniteDuration, - randomFactor: Double) + randomFactor: Double, + override val supervisorStrategy: SupervisorStrategy) extends Actor { import BackoffSupervisor._ @@ -130,6 +175,15 @@ final class BackoffSupervisor( private var child: Option[ActorRef] = None private var restartCount = 0 + // for binary compatibility with 2.4.0 + def this( + childProps: Props, + childName: String, + minBackoff: FiniteDuration, + maxBackoff: FiniteDuration, + randomFactor: Double) = + this(childProps, childName, minBackoff, maxBackoff, randomFactor, SupervisorStrategy.defaultStrategy) + override def preStart(): Unit = startChild() @@ -156,6 +210,9 @@ final class BackoffSupervisor( case GetCurrentChild ⇒ sender() ! CurrentChild(child) + case msg if child.contains(sender()) ⇒ + context.parent.forward(msg) + case msg ⇒ child match { case Some(c) ⇒ c.forward(msg) case None ⇒ context.system.deadLetters.forward(msg)