Configure exit code and Coordinated Shutdown per-reason overrides (#25186)

* Enable exit code configurability

* Rollback first approach. Implement override

* Support overriding exit-jvm and terminate-actor-sys

* Simplifies code handling overrides
This commit is contained in:
Ignasi Marimon-Clos 2018-06-06 09:45:55 +02:00 committed by Christopher Batey
parent cab48ba699
commit 9f6fc2d6da
3 changed files with 71 additions and 30 deletions

View file

@ -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)

View file

@ -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.

View file

@ -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)
}
}