Clarify typed supervision when returning a new behavior (#25163)

This commit is contained in:
Christopher Batey 2018-05-31 13:07:15 +01:00 committed by GitHub
parent ec23844db6
commit c8f4a17025
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 104 additions and 14 deletions

View file

@ -4,6 +4,7 @@
package jdocs.akka.typed.supervision;
import akka.actor.typed.ActorRef;
import akka.actor.typed.Behavior;
import akka.actor.typed.SupervisorStrategy;
import akka.actor.typed.javadsl.Behaviors;
@ -12,6 +13,40 @@ import scala.concurrent.duration.FiniteDuration;
import java.util.concurrent.TimeUnit;
public class SupervisionCompileOnlyTest {
//#wrap
interface CounterMessage { }
public static final class Increase implements CounterMessage { }
public static final class Get implements CounterMessage {
final ActorRef<Got> sender;
public Get(ActorRef<Got> sender) {
this.sender = sender;
}
}
public static final class Got {
final int n;
public Got(int n) {
this.n = n;
}
}
public static Behavior<CounterMessage> counter(int currentValue) {
return Behaviors.receive(CounterMessage.class)
.onMessage(Increase.class, (ctx, o) -> {
return counter(currentValue + 1);
})
.onMessage(Get.class, (ctx, o) -> {
o.sender.tell(new Got(currentValue));
return Behaviors.same();
})
.build();
}
//#wrap
public static Behavior<String> behavior = Behaviors.empty();
public void supervision() {
@ -28,14 +63,19 @@ public class SupervisionCompileOnlyTest {
//#restart-limit
Behaviors.supervise(behavior)
.onFailure(IllegalStateException.class, SupervisorStrategy.restartWithLimit(
10, FiniteDuration.apply(10, TimeUnit.SECONDS)
));
10, FiniteDuration.apply(10, TimeUnit.SECONDS)
));
//#restart-limit
//#multiple
Behaviors.supervise(Behaviors.supervise(behavior)
.onFailure(IllegalStateException.class, SupervisorStrategy.restart()))
.onFailure(IllegalArgumentException.class, SupervisorStrategy.stop());
.onFailure(IllegalArgumentException.class, SupervisorStrategy.stop());
//#multiple
//#top-level
Behaviors.supervise(counter(1));
//#top-level
}
}

View file

@ -4,11 +4,13 @@
package docs.akka.typed.supervision
import akka.actor.typed.SupervisorStrategy
import akka.actor.typed.ActorRef
import akka.actor.typed.{ Behavior, SupervisorStrategy }
import akka.actor.typed.scaladsl.Behaviors
import scala.concurrent.duration._
object SupervisionCompileOnlyTest {
object SupervisionCompileOnly {
val behavior = Behaviors.empty[String]
@ -34,4 +36,22 @@ object SupervisionCompileOnlyTest {
.onFailure[IllegalStateException](SupervisorStrategy.restart))
.onFailure[IllegalArgumentException](SupervisorStrategy.stop)
//#multiple
//#wrap
sealed trait Command
case class Increment(nr: Int) extends Command
case class GetCount(replyTo: ActorRef[Int]) extends Command
def counter(count: Int): Behavior[Command] = Behaviors.receiveMessage[Command] {
case Increment(nr: Int)
counter(count + nr)
case GetCount(replyTo)
replyTo ! count
Behaviors.same
}
//#wrap
//#top-level
Behaviors.supervise(counter(1))
//#top-level
}

View file

@ -1,17 +1,27 @@
# Fault Tolerance
When an actor throws an unexpected exception, a failure, while processing a message or during initialization, the actor will by default be stopped. Note that there is an important distinction between failures and validation errors:
When an actor throws an unexpected exception, a failure, while processing a message or during initialization, the actor
will by default be stopped. Note that there is an important distinction between failures and validation errors:
A validation error means that the data of a command sent to an actor is not valid, this should rather be modelled as a part of the actor protocol than make the actor throw exceptions.
A validation error means that the data of a command sent to an actor is not valid, this should rather be modelled as a
part of the actor protocol than make the actor throw exceptions.
A failure is instead something unexpected or outside the control of the actor itself, for example a database connection that broke. Opposite to validation errors, it is seldom useful to model such as parts of the protocol as a sending actor very seldom can do anything useful about it.
A failure is instead something unexpected or outside the control of the actor itself, for example a database connection
that broke. Opposite to validation errors, it is seldom useful to model such as parts of the protocol as a sending actor
very seldom can do anything useful about it.
For failures it is useful to apply the "let it crash" philosophy: instead of mixing fine grained recovery and correction of internal state that may have become partially invalid because of the failure with the business logic we move that responsibility somewhere else. For many cases the resolution can then be to "crash" the actor, and start a new one, with a fresh state that we know is valid.
For failures it is useful to apply the "let it crash" philosophy: instead of mixing fine grained recovery and correction
of internal state that may have become partially invalid because of the failure with the business logic we move that
responsibility somewhere else. For many cases the resolution can then be to "crash" the actor, and start a new one,
with a fresh state that we know is valid.
## Supervision
In Akka Typed this "somewhere else" is called supervision. Supervision allows you to declaratively describe what should happen when a certain type of exceptions are thrown inside an actor. To use supervision the actual Actor behavior is wrapped using `Behaviors.supervise`, for example to restart on `IllegalStateExceptions`:
In Akka Typed this "somewhere else" is called supervision. Supervision allows you to delaratively describe what should happen when a certain type of exceptions are thrown inside an actor. To use supervision the actual Actor behavior is wrapped using `Behaviors.supervise`, for example to restart on `IllegalStateExceptions`:
Scala
: @@snip [SupervisionCompileOnlyTest.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/supervision/SupervisionCompileOnlyTest.scala) { #restart }
: @@snip [SupervisionCompileOnly.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/supervision/SupervisionCompileOnly.scala) { #restart }
Java
: @@snip [SupervisionCompileOnlyTest.java]($akka$/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/supervision/SupervisionCompileOnlyTest.java) { #restart }
@ -19,7 +29,7 @@ Java
Or to resume, ignore the failure and process the next message, instead:
Scala
: @@snip [SupervisionCompileOnlyTest.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/supervision/SupervisionCompileOnlyTest.scala) { #resume }
: @@snip [SupervisionCompileOnly.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/supervision/SupervisionCompileOnly.scala) { #resume }
Java
: @@snip [SupervisionCompileOnlyTest.java]($akka$/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/supervision/SupervisionCompileOnlyTest.java) { #resume }
@ -28,7 +38,7 @@ More complicated restart strategies can be used e.g. to restart no more than 10
times in a 10 second period:
Scala
: @@snip [SupervisionCompileOnlyTest.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/supervision/SupervisionCompileOnlyTest.scala) { #restart-limit }
: @@snip [SupervisionCompileOnly.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/supervision/SupervisionCompileOnly.scala) { #restart-limit }
Java
: @@snip [SupervisionCompileOnlyTest.java]($akka$/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/supervision/SupervisionCompileOnlyTest.java) { #restart-limit }
@ -37,13 +47,33 @@ To handle different exceptions with different strategies calls to `supervise`
can be nested:
Scala
: @@snip [SupervisionCompileOnlyTest.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/supervision/SupervisionCompileOnlyTest.scala) { #multiple }
: @@snip [SupervisionCompileOnly.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/supervision/SupervisionCompileOnly.scala) { #multiple }
Java
: @@snip [SupervisionCompileOnlyTest.java]($akka$/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/supervision/SupervisionCompileOnlyTest.java) { #multiple }
For a full list of strategies see the public methods on `SupervisorStrategy`
### Wrapping behaviors
It is very common to store state by changing behavior e.g.
Scala
: @@snip [SupervisionCompileOnly.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/supervision/SupervisionCompileOnly.scala) { #wrap }
Java
: @@snip [SupervisionCompileOnlyTest.java]($akka$/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/supervision/SupervisionCompileOnlyTest.java) { #wrap }
When doing this supervision only needs to be added to the top level:
Scala
: @@snip [SupervisionCompileOnly.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/supervision/SupervisionCompileOnly.scala) { #top-level }
Java
: @@snip [SupervisionCompileOnlyTest.java]($akka$/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/supervision/SupervisionCompileOnlyTest.java) { #top-level }
Each returned behavior will be re-wrapped automatically with the supervisor.
## Bubble failures up through the hierarchy