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 66f937591c..02023d8547 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 ce6002f338..9fa6487034 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 a2003ba439..58617d8daa 100644 --- a/akka-docs/rst/java/lambda-persistence.rst +++ b/akka-docs/rst/java/lambda-persistence.rst @@ -302,7 +302,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 b8470651e4..668c915bac 100644 --- a/akka-docs/rst/java/persistence.rst +++ b/akka-docs/rst/java/persistence.rst @@ -305,7 +305,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 36c621b165..47b61b9c32 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 0eaad129fd..f57b8f2d0c 100644 --- a/akka-docs/rst/scala/persistence.rst +++ b/akka-docs/rst/scala/persistence.rst @@ -291,7 +291,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._