diff --git a/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/supervision/SupervisionCompileOnlyTest.java b/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/supervision/SupervisionCompileOnlyTest.java index 56c7d528ca..70fd828242 100644 --- a/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/supervision/SupervisionCompileOnlyTest.java +++ b/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/supervision/SupervisionCompileOnlyTest.java @@ -4,9 +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.*; import akka.actor.typed.javadsl.Behaviors; import scala.concurrent.duration.FiniteDuration; @@ -102,7 +100,7 @@ public class SupervisionCompileOnlyTest { return Behaviors.receiveMessage( msg -> { - // there might be bugs here... + // message handling that might throw an exception String[] parts = msg.split(" "); child1.tell(parts[0]); child2.tell(parts[1]); @@ -124,7 +122,7 @@ public class SupervisionCompileOnlyTest { return Behaviors.supervise( Behaviors.receiveMessage( msg -> { - // there might be bugs here... + // message handling that might throw an exception String[] parts = msg.split(" "); child1.tell(parts[0]); child2.tell(parts[1]); @@ -135,4 +133,47 @@ public class SupervisionCompileOnlyTest { } // #restart-keep-children + interface Resource { + void close(); + + void process(String[] parts); + } + + public static Resource claimResource() { + return null; + } + + static void prerestartBehavior() { + // #restart-PreRestart-signal + Behaviors.supervise( + Behaviors.setup( + ctx -> { + final Resource resource = claimResource(); + + return Behaviors.receive(String.class) + .onMessage( + String.class, + msg -> { + // message handling that might throw an exception + String[] parts = msg.split(" "); + resource.process(parts); + return Behaviors.same(); + }) + .onSignal( + PreRestart.class, + signal -> { + resource.close(); + return Behaviors.same(); + }) + .onSignal( + PostStop.class, + signal -> { + resource.close(); + return Behaviors.same(); + }) + .build(); + })) + .onFailure(Exception.class, SupervisorStrategy.restart()); + // #restart-PreRestart-signal + } } diff --git a/akka-actor-typed-tests/src/test/scala/docs/akka/typed/supervision/SupervisionCompileOnly.scala b/akka-actor-typed-tests/src/test/scala/docs/akka/typed/supervision/SupervisionCompileOnly.scala index a3beb1a131..1df85c7299 100644 --- a/akka-actor-typed-tests/src/test/scala/docs/akka/typed/supervision/SupervisionCompileOnly.scala +++ b/akka-actor-typed-tests/src/test/scala/docs/akka/typed/supervision/SupervisionCompileOnly.scala @@ -5,8 +5,12 @@ package docs.akka.typed.supervision import akka.actor.typed.ActorRef +import akka.actor.typed.PostStop +import akka.actor.typed.PreRestart import akka.actor.typed.{ Behavior, SupervisorStrategy } import akka.actor.typed.scaladsl.Behaviors +import com.github.ghik.silencer.silent + import scala.concurrent.duration._ object SupervisionCompileOnly { @@ -68,7 +72,7 @@ object SupervisionCompileOnly { val child2 = ctx.spawn(child(0), "child2") Behaviors.receiveMessage[String] { msg => - // there might be bugs here... + // message handling that might throw an exception val parts = msg.split(" ") child1 ! parts(0) child2 ! parts(1) @@ -90,7 +94,7 @@ object SupervisionCompileOnly { Behaviors .supervise { Behaviors.receiveMessage[String] { msg => - // there might be bugs here... + // message handling that might throw an exception val parts = msg.split(" ") child1 ! parts(0) child2 ! parts(1) @@ -101,4 +105,38 @@ object SupervisionCompileOnly { } } //#restart-keep-children + + trait Resource { + def close(): Unit + def process(parts: Array[String]): Unit + } + def claimResource(): Resource = ??? + + @silent("never used") + //#restart-PreRestart-signal + def withPreRestart: Behavior[String] = { + Behaviors + .supervise[String] { + Behaviors.setup { ctx => + val resource = claimResource() + + Behaviors + .receiveMessage[String] { msg => + // message handling that might throw an exception + + val parts = msg.split(" ") + resource.process(parts) + Behaviors.same + } + .receiveSignal { + case (_, signal) if signal == PreRestart || signal == PostStop => + resource.close() + Behaviors.same + } + } + } + .onFailure[Exception](SupervisorStrategy.restart) + } + + //#restart-PreRestart-signal } diff --git a/akka-docs/src/main/paradox/typed/fault-tolerance.md b/akka-docs/src/main/paradox/typed/fault-tolerance.md index 5fbe3e64c4..0b0cc6b87f 100644 --- a/akka-docs/src/main/paradox/typed/fault-tolerance.md +++ b/akka-docs/src/main/paradox/typed/fault-tolerance.md @@ -122,6 +122,18 @@ Java That means that the `setup` block will only be run when the parent actor is first started, and not when it is restarted. +## The PreRestart signal + +Before a supervised actor is restarted it is sent the @apidoc[akka.actor.typed.PreRestart] signal giving it a chance to clean up resources +it has created, much like the @apidoc[akka.actor.typed.PostStop] signal when the actor stops. +The returned behavior from the `PreRestart` signal is ignored. + +Scala +: @@snip [SupervisionCompileOnly.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/supervision/SupervisionCompileOnly.scala) { #restart-PreRestart-signal } + +Java +: @@snip [SupervisionCompileOnlyTest.java](/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/supervision/SupervisionCompileOnlyTest.java) { #restart-PreRestart-signal } + ## Bubble failures up through the hierarchy In some scenarios it may be useful to push the decision about what to do on a failure upwards in the Actor hierarchy