I need to do some initialization of my Ktor app, but I want to do it only after Netty is ready to accept connections. From the other hand, I don't want such initialization to happen if Netty failed to start (with the typical "address already in use", for example).
I implemented the straightforward approach (see below), but I wonder if it possible to make it the less ugly way?
First I save the reference to NettyApplicationEngine:
embeddedServer = embeddedServer(Netty, port, module)
Then I use the channels
field from NettyApplicationEngine to determine its state:
private fun NettyApplicationEngine.channelsReady(): Boolean {
val channelsField = this::class.members.find { it.name == "channels" }!!
channelsField.isAccessible = true
val channels = channelsField.call(this) as List<Channel>?
return !channels.isNullOrEmpty() && channels.all { it.isActive }
}
And, finally, I catch the ApplicationStarted event and spin until channels are ready:
environment.monitor.subscribe(ApplicationStarted) {
thread(start = true, name = "real netty postinit") {
for (i in 1..100) {
TimeUnit.MILLISECONDS.sleep(100)
if (embeddedServer.channelsReady()) break
}
if (embeddedServer.channelsReady()) {
// Initialization here
} else {
// Server didn't start
embeddedServer.stop(1, 1, TimeUnit.SECONDS)
}
}
}
After trying several different approaches, I concluded with a self-test which simply sends HTTP request to my own endpoint and if both HttpClient and route handler performed successfully, I consider Netty is ready.
First I register Routing.RoutingCallFinished
event with NettyApplicationEngine.environment.monitor
(I dispose
the created handler later).
Then I iterate all NettyApplicationEngine.environment.connectors
and create Deferred
s that will be completed from the RoutingCallFinished
handler. Also, I launch async coroutines that will check corresponding endpoints with HttpClient.
After that I awaitAll
on those Deferred
s (and on Deferred
from ApplicationStarted
event handler as well).