Search code examples
kotlinnettyktor

How to check inside Ktor that the Netty is actually started?


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

Solution

  • 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 Deferreds 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 Deferreds (and on Deferred from ApplicationStarted event handler as well).