Skip to content

Commit

Permalink
Clarify typed supervision when returning a new behavior
Browse files Browse the repository at this point in the history
  • Loading branch information
chbatey committed May 29, 2018
1 parent 84d53d1 commit 7a1eefd
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -12,6 +13,40 @@
import java.util.concurrent.TimeUnit;

public class SupervisionCompileOnlyTest {
//#wrap
interface CounterMessage { }

static final class Increase implements CounterMessage { }

static final class Get implements CounterMessage {
final ActorRef<Got> sender;

public Get(ActorRef<Got> sender) {
this.sender = sender;
}
}

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() {
Expand All @@ -28,14 +63,19 @@ public void supervision() {
//#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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand All @@ -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
}
48 changes: 39 additions & 9 deletions akka-docs/src/main/paradox/typed/fault-tolerance.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,35 @@
# 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 }

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 }
Expand All @@ -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 }
Expand All @@ -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

Expand Down

0 comments on commit 7a1eefd

Please sign in to comment.