@@ -12,6 +12,9 @@ import misk.inject.getInstance
1212import misk.web.jetty.JettyHealthService
1313import misk.web.jetty.JettyService
1414import wisp.logging.getLogger
15+ import java.io.Closeable
16+ import java.util.concurrent.TimeUnit
17+ import java.util.concurrent.TimeoutException
1518import kotlin.concurrent.thread
1619import kotlin.system.measureTimeMillis
1720import kotlin.time.Duration.Companion.milliseconds
@@ -60,7 +63,6 @@ class MiskApplication private constructor(
6063 *
6164 * If no command line arguments are specified, the service starts and blocks until terminated.
6265 */
63- @VisibleForTesting
6466 internal fun doRun (args : Array <String >) {
6567 if (args.isEmpty()) {
6668 startServiceAndAwaitTermination()
@@ -79,7 +81,7 @@ class MiskApplication private constructor(
7981 binder().requireAtInjectOnConstructors()
8082 }
8183 },
82- * command.modules.toTypedArray()
84+ * command.modules.toTypedArray(),
8385 )
8486
8587 injector.injectMembers(command)
@@ -101,18 +103,36 @@ class MiskApplication private constructor(
101103 * exiting the process.
102104 */
103105 @VisibleForTesting
104- internal lateinit var shutdownHook : Thread
106+ internal lateinit var shutdownHook: Thread
105107
106108 /* *
107109 * Provides internal testing the ability to get instances this used by the application.
108110 */
109111 @VisibleForTesting
110- internal lateinit var injector : Injector
112+ internal lateinit var injector: Injector
111113
112- private fun startServiceAndAwaitTermination () {
114+ internal fun start (): RunningMiskApplication {
113115 log.info { " creating application injector" }
114116 injector = injectorGenerator()
115117 val serviceManager = injector.getInstance<ServiceManager >()
118+
119+
120+ // We manage JettyHealthService outside ServiceManager because it must start and
121+ // shutdown last to keep the container alive via liveness checks.
122+ val jettyHealthService: JettyHealthService
123+ measureTimeMillis {
124+ log.info { " starting services" }
125+ serviceManager.startAsync()
126+ serviceManager.awaitHealthy()
127+
128+ // Start Health Service Last to ensure any dependencies are started.
129+ jettyHealthService = injector.getInstance<JettyHealthService >()
130+ jettyHealthService.startAsync()
131+ jettyHealthService.awaitRunning()
132+ }.also {
133+ log.info { " all services started successfully in ${it.milliseconds} " }
134+ }
135+
116136 shutdownHook = thread(start = false ) {
117137 measureTimeMillis {
118138 log.info { " received a shutdown hook! performing an orderly shutdown" }
@@ -135,29 +155,40 @@ class MiskApplication private constructor(
135155 log.info { " orderly shutdown complete in ${it.milliseconds} " }
136156 }
137157 }
158+ val shutdown: RunningMiskApplication = object : RunningMiskApplication {
159+ override fun stop () {
160+ shutdownHook.start()
161+ }
138162
139- Runtime .getRuntime().addShutdownHook(shutdownHook)
140-
141- // We manage JettyHealthService outside ServiceManager because it must start and
142- // shutdown last to keep the container alive via liveness checks.
163+ override fun awaitTerminated () {
164+ serviceManager.awaitStopped()
165+ jettyHealthService.awaitTerminated()
166+ log.info { " all services stopped" }
167+ }
143168
144- val jettyHealthService: JettyHealthService
145- measureTimeMillis {
146- log.info { " starting services" }
147- serviceManager.startAsync()
148- serviceManager.awaitHealthy()
169+ override fun awaitTerminated (time : Long , timeUnit : TimeUnit ) : Boolean {
170+ val deadline : Long = timeUnit.toMillis(time) + System .currentTimeMillis()
171+ try {
172+ serviceManager.awaitStopped(time, timeUnit)
173+ jettyHealthService.awaitTerminated(deadline - System .currentTimeMillis(), TimeUnit .MILLISECONDS )
174+ } catch (_ : TimeoutException ) {
175+ return false
176+ }
177+ log.info { " all services stopped" }
178+ return true
179+ }
149180
150- // Start Health Service Last to ensure any dependencies are started.
151- jettyHealthService = injector.getInstance<JettyHealthService >()
152- jettyHealthService.startAsync()
153- jettyHealthService.awaitRunning()
154- }.also {
155- log.info { " all services started successfully in ${it.milliseconds} " }
181+ override fun app (): MiskApplication {
182+ return this @MiskApplication
183+ }
156184 }
185+ return shutdown
186+ }
157187
158- serviceManager.awaitStopped()
159- jettyHealthService.awaitTerminated()
160- log.info { " all services stopped" }
188+ private fun startServiceAndAwaitTermination () {
189+ val app = start()
190+ Runtime .getRuntime().addShutdownHook(shutdownHook)
191+ app.awaitTerminated()
161192 }
162193
163194 private companion object {
@@ -166,3 +197,13 @@ class MiskApplication private constructor(
166197
167198 internal class CliException (message : String ) : RuntimeException(message)
168199}
200+
201+
202+ internal interface RunningMiskApplication {
203+ fun stop ()
204+
205+ fun awaitTerminated ()
206+
207+ fun app () : MiskApplication
208+ fun awaitTerminated (i : Long , seconds : TimeUnit ): Boolean
209+ }
0 commit comments