diff --git a/akka-actor-tests/src/test/scala/akka/actor/CoordinatedShutdownSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/CoordinatedShutdownSpec.scala index 3554b006cc..2b91704538 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/CoordinatedShutdownSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/CoordinatedShutdownSpec.scala @@ -323,6 +323,18 @@ class CoordinatedShutdownSpec extends AkkaSpec(ConfigFactory.parseString( "c" → Phase(dependsOn = Set("a", "b"), timeout = 10.seconds, recover = false, enabled = true))) } + "default exit code to 0" in { + lazy val conf = ConfigFactory.load().getConfig("akka.coordinated-shutdown") + val confWithOverrides = CoordinatedShutdown.confWithOverrides(conf, None) + confWithOverrides.getInt("exit-code") should ===(0) + } + + "default exit code to -1 when the Reason is ClusterDowning" in { + lazy val conf = ConfigFactory.load().getConfig("akka.coordinated-shutdown") + val confWithOverrides = CoordinatedShutdown.confWithOverrides(conf, Some(CoordinatedShutdown.ClusterDowningReason)) + confWithOverrides.getInt("exit-code") should ===(-1) + } + // this must be the last test, since it terminates the ActorSystem "terminate ActorSystem" in { Await.result(CoordinatedShutdown(system).run(CustomReason), 10.seconds) should ===(Done) diff --git a/akka-actor/src/main/resources/reference.conf b/akka-actor/src/main/resources/reference.conf index f1d550b82e..da3efd923a 100644 --- a/akka-actor/src/main/resources/reference.conf +++ b/akka-actor/src/main/resources/reference.conf @@ -1056,11 +1056,28 @@ akka { # immediately when the last phase is reached. exit-jvm = off + # Exit status to use on System.exit(int) when 'exit-jvm' is 'on'. + exit-code = 0 + # Run the coordinated shutdown when the JVM process exits, e.g. # via kill SIGTERM signal (SIGINT ctrl-c doesn't work). # This property is related to `akka.jvm-shutdown-hooks` above. run-by-jvm-shutdown-hook = on + # When Coordinated Shutdown is triggered an instance of `Reason` is + # required. That value can be used to override the default settings. + # Only 'exit-jvm', 'exit-code' and 'terminate-actor-system' may be + # overriden depending on the reason. + reason-overrides { + # Overrides are applied using the `reason.getClass.getName`. This + # default overrides the `exit-code` when the `Reason` is a cluster + # Downing event (identified by the object + # "akka.actor.CoordinatedShutdown$ClusterDowningReason$"). + "akka.actor.CoordinatedShutdown$ClusterDowningReason$" { + exit-code = -1 + } + } + #//#coordinated-shutdown-phases # CoordinatedShutdown is enabled by default and will run the tasks that # are added to these phases by individual Akka modules and user logic. diff --git a/akka-actor/src/main/scala/akka/actor/CoordinatedShutdown.scala b/akka-actor/src/main/scala/akka/actor/CoordinatedShutdown.scala index 4d4bfb0453..c194348151 100644 --- a/akka-actor/src/main/scala/akka/actor/CoordinatedShutdown.scala +++ b/akka-actor/src/main/scala/akka/actor/CoordinatedShutdown.scala @@ -30,6 +30,7 @@ import java.util.concurrent.atomic.AtomicReference import java.util.function.Supplier import java.util.Optional +import akka.annotation.InternalApi import akka.util.OptionVal object CoordinatedShutdown extends ExtensionId[CoordinatedShutdown] with ExtensionIdProvider { @@ -171,38 +172,49 @@ object CoordinatedShutdown extends ExtensionId[CoordinatedShutdown] with Extensi coord } - private def initPhaseActorSystemTerminate(system: ActorSystem, conf: Config, coord: CoordinatedShutdown): Unit = { - val terminateActorSystem = conf.getBoolean("terminate-actor-system") - val exitJvm = conf.getBoolean("exit-jvm") - if (terminateActorSystem || exitJvm) { - coord.addTask(PhaseActorSystemTerminate, "terminate-system") { () ⇒ - if (exitJvm && terminateActorSystem) { - // In case ActorSystem shutdown takes longer than the phase timeout, - // exit the JVM forcefully anyway. - // We must spawn a separate thread to not block current thread, - // since that would have blocked the shutdown of the ActorSystem. - val timeout = coord.timeout(PhaseActorSystemTerminate) - val t = new Thread { - override def run(): Unit = { - if (Try(Await.ready(system.whenTerminated, timeout)).isFailure && !runningJvmHook) - System.exit(0) - } - } - t.setName("CoordinatedShutdown-exit") - t.start() - } + // locate reason-specific overrides and merge with defaults. + @InternalApi private[akka] def confWithOverrides(conf: Config, reason: Option[Reason]): Config = { + reason.flatMap { r ⇒ + val basePath = s"""reason-overrides."${r.getClass.getName}"""" + if (conf.hasPath(basePath)) Some(conf.getConfig(basePath).withFallback(conf)) else None + }.getOrElse( + conf + ) + } - if (terminateActorSystem) { - system.terminate().map { _ ⇒ - if (exitJvm && !runningJvmHook) System.exit(0) - Done - }(ExecutionContexts.sameThreadExecutionContext) - } else if (exitJvm) { - System.exit(0) - Future.successful(Done) - } else - Future.successful(Done) + private def initPhaseActorSystemTerminate(system: ActorSystem, conf: Config, coord: CoordinatedShutdown): Unit = { + coord.addTask(PhaseActorSystemTerminate, "terminate-system") { () ⇒ + val confForReason = confWithOverrides(conf, coord.shutdownReason()) + val terminateActorSystem = confForReason.getBoolean("terminate-actor-system") + val exitJvm = confForReason.getBoolean("exit-jvm") + val exitCode = confForReason.getInt("exit-code") + + if (exitJvm && terminateActorSystem) { + // In case ActorSystem shutdown takes longer than the phase timeout, + // exit the JVM forcefully anyway. + // We must spawn a separate thread to not block current thread, + // since that would have blocked the shutdown of the ActorSystem. + val timeout = coord.timeout(PhaseActorSystemTerminate) + val t = new Thread { + override def run(): Unit = { + if (Try(Await.ready(system.whenTerminated, timeout)).isFailure && !runningJvmHook) + System.exit(exitCode) + } + } + t.setName("CoordinatedShutdown-exit") + t.start() } + + if (terminateActorSystem) { + system.terminate().map { _ ⇒ + if (exitJvm && !runningJvmHook) System.exit(exitCode) + Done + }(ExecutionContexts.sameThreadExecutionContext) + } else if (exitJvm) { + System.exit(exitCode) + Future.successful(Done) + } else + Future.successful(Done) } }