diff --git a/akka-persistence/src/main/scala/akka/persistence/BackoffSupervisor.scala b/akka-actor/src/main/scala/akka/pattern/BackoffSupervisor.scala similarity index 83% rename from akka-persistence/src/main/scala/akka/persistence/BackoffSupervisor.scala rename to akka-actor/src/main/scala/akka/pattern/BackoffSupervisor.scala index 47e2bde299..90b17ea725 100644 --- a/akka-persistence/src/main/scala/akka/persistence/BackoffSupervisor.scala +++ b/akka-actor/src/main/scala/akka/pattern/BackoffSupervisor.scala @@ -1,12 +1,11 @@ /** * Copyright (C) 2015 Typesafe Inc. */ -package akka.persistence +package akka.pattern import scala.concurrent.duration.FiniteDuration import scala.concurrent.forkjoin.ThreadLocalRandom import akka.actor.Actor -import akka.actor.ActorLogging import akka.actor.ActorRef import akka.actor.DeadLetterSuppression import akka.actor.Props @@ -17,6 +16,8 @@ import scala.concurrent.duration.Duration object BackoffSupervisor { /** + * Props for creating an [[BackoffSupervisor]] actor. + * * @param childProps the [[akka.actor.Props]] of the child actor that * will be started and supervised * @param childName name of the child actor @@ -24,8 +25,8 @@ object BackoffSupervisor { * 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 + * 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`. */ def props( childProps: Props, @@ -64,8 +65,14 @@ object BackoffSupervisor { /** * This actor can be used to supervise a child actor and start it again - * after a back-off duration if the child actor is stopped. This is useful - * for persistent actors, which are stopped in case of persistence failures. + * after a back-off duration if the child actor is stopped. + * + * This is useful in situations where the re-start of the child actor should be + * delayed e.g. in order to give an external resource time to recover before the + * child actor tries contacting it again (after being restarted). + * + * Specifically this pattern is useful for for persistent actors, + * which are stopped in case of persistence failures. * Just restarting them immediately would probably fail again (since the data * store is probably unavailable). It is better to try again after a delay. * @@ -81,13 +88,13 @@ object BackoffSupervisor { * actors hit the backend resource at the same time. * * You can retrieve the current child `ActorRef` by sending `BackoffSupervisor.GetCurrentChild` - * message to this actor and it will reply with [[BackoffSupervisor.CurrentChild]] containing the - * `ActorRef` of the current child, if any. + * 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 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 want to do an intentional stop. + * if it wants to do an intentional stop. */ final class BackoffSupervisor( childProps: Props, @@ -119,7 +126,7 @@ final class BackoffSupervisor( if (restartCount >= 30) // Duration overflow protection (> 100 years) maxBackoff else - (maxBackoff.min(minBackoff * math.pow(2, restartCount)) * rnd) match { + maxBackoff.min(minBackoff * math.pow(2, restartCount)) * rnd match { case f: FiniteDuration ⇒ f case _ ⇒ maxBackoff } diff --git a/akka-cluster-sharding/src/main/scala/akka/cluster/sharding/ClusterSharding.scala b/akka-cluster-sharding/src/main/scala/akka/cluster/sharding/ClusterSharding.scala index 390c327062..68b403b75d 100644 --- a/akka-cluster-sharding/src/main/scala/akka/cluster/sharding/ClusterSharding.scala +++ b/akka-cluster-sharding/src/main/scala/akka/cluster/sharding/ClusterSharding.scala @@ -20,7 +20,7 @@ import akka.actor.PoisonPill import akka.actor.Props import akka.cluster.Cluster import akka.cluster.singleton.ClusterSingletonManager -import akka.persistence.BackoffSupervisor +import akka.pattern.BackoffSupervisor import akka.util.ByteString import akka.pattern.ask import akka.dispatch.Dispatchers diff --git a/akka-cluster-sharding/src/multi-jvm/scala/akka/cluster/sharding/ClusterShardingSpec.scala b/akka-cluster-sharding/src/multi-jvm/scala/akka/cluster/sharding/ClusterShardingSpec.scala index 5ae3d75e3b..23e09f3093 100644 --- a/akka-cluster-sharding/src/multi-jvm/scala/akka/cluster/sharding/ClusterShardingSpec.scala +++ b/akka-cluster-sharding/src/multi-jvm/scala/akka/cluster/sharding/ClusterShardingSpec.scala @@ -11,6 +11,7 @@ import language.postfixOps import scala.concurrent.duration._ import com.typesafe.config.ConfigFactory import akka.actor._ +import akka.pattern.BackoffSupervisor import akka.cluster.Cluster import akka.persistence.PersistentActor import akka.persistence.Persistence @@ -26,7 +27,7 @@ import java.io.File import org.apache.commons.io.FileUtils import akka.cluster.singleton.ClusterSingletonManager import akka.cluster.singleton.ClusterSingletonManagerSettings -import akka.persistence.BackoffSupervisor +import akka.pattern.BackoffSupervisor object ClusterShardingSpec extends MultiNodeConfig { val controller = role("controller") diff --git a/akka-docs/rst/general/supervision.rst b/akka-docs/rst/general/supervision.rst index baf9d2d2c1..a047fdb111 100644 --- a/akka-docs/rst/general/supervision.rst +++ b/akka-docs/rst/general/supervision.rst @@ -199,6 +199,37 @@ external resource, which may also be one of its own children. If a third party terminates a child by way of the ``system.stop(child)`` method or sending a :class:`PoisonPill`, the supervisor might well be affected. +.. _backoff-supervisor: + +Delayed restarts with the BackoffSupervisor pattern +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Provided as a build-in pattern the ``akka.pattern.BackoffSupervisor`` actor implements the so-called +*exponential backoff supervision strategy*, which can be used to death-watch an actor, +and when it terminates try to start it again, each time with a growing time delay between those restarts. + +This pattern is useful when the started actor fails because some external resource is not available, +and we need to give it some time to start-up again. One of the prime examples when this is useful is +when a :ref:`PersistentActor ` fails with an persistence failure - which indicates that +the database may be down or overloaded, in such situations it makes most sense to give it a little bit of time +to recover before the peristent actor is restarted. + +The following Scala snippet shows how to create a backoff supervisor which will start the given echo actor +in increasing intervals of 3, 6, 12, 24 and finally 30 seconds: + +.. includecode:: ../scala/code/docs/pattern/BackoffSupervisorDocSpec.scala#backoff + +The above is equivalent to this Java code: + +.. includecode:: ../java/code/docs/pattern/BackoffSupervisorDocTest.java#backoff-imports +.. includecode:: ../java/code/docs/pattern/BackoffSupervisorDocTest.java#backoff + +Using a ``randomFactor`` to add a little bit of additional variance to the backoff intervals +is highly recommended, in order to avoid multiple actors re-start at the exact same point in time, +for example because they were stopped due to a shared resource such as a database going down +and re-starting after the same configured interval. By adding additional randomness to the +re-start intervals the actors will start in slightly different points in time, thus avoiding +large spikes of traffic hitting the recovering shared database or other resource that they all need to contact. + One-For-One Strategy vs. All-For-One Strategy --------------------------------------------- diff --git a/akka-docs/rst/java/code/docs/pattern/BackoffSupervisorDocTest.java b/akka-docs/rst/java/code/docs/pattern/BackoffSupervisorDocTest.java new file mode 100644 index 0000000000..a5c103f746 --- /dev/null +++ b/akka-docs/rst/java/code/docs/pattern/BackoffSupervisorDocTest.java @@ -0,0 +1,32 @@ +/** + * Copyright (C) 2009-2015 Typesafe Inc. + */ +package docs.pattern; + +import akka.actor.*; +import akka.pattern.BackoffSupervisor; +import akka.testkit.TestActors.EchoActor; +//#backoff-imports +import scala.concurrent.duration.Duration; +//#backoff-imports + +import java.util.concurrent.TimeUnit; + +public class BackoffSupervisorDocTest { + + void example (ActorSystem system) { + //#backoff + final Props childProps = Props.create(EchoActor.class); + + final Props supervisorProps = BackoffSupervisor.props( + childProps, + "myEcho", + Duration.create(3, TimeUnit.SECONDS), + Duration.create(30, TimeUnit.SECONDS), + 0.2); // adds 20% "noise" to vary the intervals slightly + + system.actorOf(supervisorProps, "echoSupervisor"); + //#backoff + } + +} diff --git a/akka-docs/rst/java/code/docs/persistence/LambdaPersistenceDocTest.java b/akka-docs/rst/java/code/docs/persistence/LambdaPersistenceDocTest.java index d5f3207b43..b57cc0e92a 100644 --- a/akka-docs/rst/java/code/docs/persistence/LambdaPersistenceDocTest.java +++ b/akka-docs/rst/java/code/docs/persistence/LambdaPersistenceDocTest.java @@ -10,6 +10,7 @@ import akka.actor.ActorSystem; import akka.actor.Props; import akka.japi.Procedure; import akka.japi.pf.ReceiveBuilder; +import akka.pattern.BackoffSupervisor; import akka.persistence.*; import scala.Option; import scala.concurrent.duration.Duration; diff --git a/akka-docs/rst/java/code/docs/persistence/PersistenceDocTest.java b/akka-docs/rst/java/code/docs/persistence/PersistenceDocTest.java index e9963615d7..19175c1574 100644 --- a/akka-docs/rst/java/code/docs/persistence/PersistenceDocTest.java +++ b/akka-docs/rst/java/code/docs/persistence/PersistenceDocTest.java @@ -5,6 +5,8 @@ package docs.persistence; import java.util.concurrent.TimeUnit; + +import akka.pattern.BackoffSupervisor; import scala.concurrent.duration.Duration; import akka.actor.ActorPath; import akka.actor.ActorRef; diff --git a/akka-docs/rst/java/lambda-persistence.rst b/akka-docs/rst/java/lambda-persistence.rst index 346d08736d..05782e7cb5 100644 --- a/akka-docs/rst/java/lambda-persistence.rst +++ b/akka-docs/rst/java/lambda-persistence.rst @@ -304,7 +304,7 @@ and the actor will unconditionally be stopped. The reason that it cannot resume when persist fails is that it is unknown if the even was actually persisted or not, and therefore it is in an inconsistent state. Restarting on persistent failures will most likely fail anyway, since the journal is probably unavailable. It is better to stop the -actor and after a back-off timeout start it again. The ``akka.persistence.BackoffSupervisor`` actor +actor and after a back-off timeout start it again. The ``akka.pattern.BackoffSupervisor`` actor is provided to support such restarts. .. includecode:: code/docs/persistence/LambdaPersistenceDocTest.java#backoff diff --git a/akka-docs/rst/java/persistence.rst b/akka-docs/rst/java/persistence.rst index a1e69ee8e4..06646e2273 100644 --- a/akka-docs/rst/java/persistence.rst +++ b/akka-docs/rst/java/persistence.rst @@ -307,7 +307,7 @@ and the actor will unconditionally be stopped. The reason that it cannot resume when persist fails is that it is unknown if the even was actually persisted or not, and therefore it is in an inconsistent state. Restarting on persistent failures will most likely fail anyway, since the journal is probably unavailable. It is better to stop the -actor and after a back-off timeout start it again. The ``akka.persistence.BackoffSupervisor`` actor +actor and after a back-off timeout start it again. The ``akka.pattern.BackoffSupervisor`` actor is provided to support such restarts. .. includecode:: code/docs/persistence/PersistenceDocTest.java#backoff diff --git a/akka-docs/rst/scala/code/docs/pattern/BackoffSupervisorDocSpec.scala b/akka-docs/rst/scala/code/docs/pattern/BackoffSupervisorDocSpec.scala new file mode 100644 index 0000000000..ca7666191b --- /dev/null +++ b/akka-docs/rst/scala/code/docs/pattern/BackoffSupervisorDocSpec.scala @@ -0,0 +1,31 @@ +/** + * Copyright (C) 2009-2015 Typesafe Inc. + */ + +package docs.pattern + +import akka.actor.{ActorSystem, Props} +import akka.pattern.BackoffSupervisor +import akka.testkit.TestActors.EchoActor + +class BackoffSupervisorDocSpec { + + class BackoffSupervisorDocSpecExample { + val system: ActorSystem = ??? + import scala.concurrent.duration._ + + //#backoff + val childProps = Props(classOf[EchoActor]) + + val supervisor = BackoffSupervisor.props( + childProps, + childName = "myEcho", + minBackoff = 3.seconds, + maxBackoff = 30.seconds, + randomFactor = 0.2) // adds 20% "noise" to vary the intervals slightly + + system.actorOf(supervisor, name = "echoSupervisor") + //#backoff + } + +} diff --git a/akka-docs/rst/scala/code/docs/persistence/PersistenceDocSpec.scala b/akka-docs/rst/scala/code/docs/persistence/PersistenceDocSpec.scala index 7228b31be3..68878fdaef 100644 --- a/akka-docs/rst/scala/code/docs/persistence/PersistenceDocSpec.scala +++ b/akka-docs/rst/scala/code/docs/persistence/PersistenceDocSpec.scala @@ -5,6 +5,7 @@ package docs.persistence import akka.actor.{ Actor, ActorRef, ActorSystem, Props } +import akka.pattern.BackoffSupervisor import akka.persistence._ import scala.concurrent.duration._ diff --git a/akka-docs/rst/scala/persistence.rst b/akka-docs/rst/scala/persistence.rst index 34c880e020..ade378cf4b 100644 --- a/akka-docs/rst/scala/persistence.rst +++ b/akka-docs/rst/scala/persistence.rst @@ -293,7 +293,7 @@ and the actor will unconditionally be stopped. The reason that it cannot resume when persist fails is that it is unknown if the even was actually persisted or not, and therefore it is in an inconsistent state. Restarting on persistent failures will most likely fail anyway, since the journal is probably unavailable. It is better to stop the -actor and after a back-off timeout start it again. The ``akka.persistence.BackoffSupervisor`` actor +actor and after a back-off timeout start it again. The ``akka.pattern.BackoffSupervisor`` actor is provided to support such restarts. .. includecode:: code/docs/persistence/PersistenceDocSpec.scala#backoff diff --git a/akka-persistence/src/test/scala/akka/persistence/BackoffSupervisorSpec.scala b/akka-persistence/src/test/scala/akka/persistence/BackoffSupervisorSpec.scala index 6543ceb7cf..6caf2b700f 100644 --- a/akka-persistence/src/test/scala/akka/persistence/BackoffSupervisorSpec.scala +++ b/akka-persistence/src/test/scala/akka/persistence/BackoffSupervisorSpec.scala @@ -4,6 +4,8 @@ package akka.persistence +import akka.pattern.BackoffSupervisor + import scala.concurrent.duration._ import akka.actor._ import akka.testkit._