👌

Ktorのauto-reload時にpluginエラーがあると応答しなくなり、原因究明ができない問題のワークアラウンド

2024/02/25に公開

前提とするバージョン

io.ktor:ktor-server-core-jvm:2.3.8
io.ktor:ktor-server-netty-jvm:2.3.8

問題

development = trueによるAuto-reload中に、プラグインにエラーが発生すると、ログが何もでず、Ktorが応答しなくなる。
issueは、以下で起票済みです。
Ktor becoming unresponsive when a plugin errors during auto-reload. : KTOR-6799

auto-reload時にエラーとなるプラグインに気づく方法

auto-reloadのときは、embeddedServerの戻り値がApplicationEngineEnvironmentReloadingというクラスになる。
このクラスにio.ktor.server.engine.ApplicationEngineEnvironmentReloading#reloadが生えているので、サーバー起動後にそれを実行することでエラーになってくれるので、問題のあるプラグインに気づくことができる。

では、なぜ実際のauto-reloadではなんでエラーのログが出ないのか?

と思うことでしょう。
前提の説明として、実際のauto-reload時には、reloadメソッドとは別のio.ktor.server.engine.ApplicationEngineEnvironmentReloading#currentApplicationというメソッドで処理がされていて、ここで必要であればreloadをするという処理が行われているらしい(これ自体は問題ない)。
で、currentApplicationを呼び出す側のエラーハンドリングがしっかりとされていないまま、Nettyを終了しているのが大まかな原因のようです。

ktorがauto-reload時にpluginでエラーになると、Nettyがio.netty.channel.AbstractChannelHandlerContext#invokeChannelRead(java.lang.Object)から例外がスローされ、io.netty.channel.AbstractChannelHandlerContext#invokeExceptionCaught(java.lang.Throwable)で、エラーをキャッチしているようです。キャッチした後は、io.ktor.server.netty.http1.NettyHttp1Handler#exceptionCaughtへ戻ってきて、エラー処理は何もせずに、io.ktor.server.netty.http1.NettyHttp1Handler#exceptionCaughtで、io.netty.channel.ChannelHandlerContext#close()を呼んでNettyを終了しているというのが原因です。

ワークアラウンド

start(await = true)での例です。

fun main() {
    val server = embeddedServer(
        Netty,
        commandLineEnvironment(arrayOf("-config=application.conf"))
    )

    // 自動リロードできないプラグインのエラーに気づけます
    ensureReloadableAsync(server.environment)

    server.start(wait = true)
}

private fun ensureReloadableAsync(environment: ApplicationEngineEnvironment) {
    Thread {
        Thread.sleep(1_000)
        if (environment is ApplicationEngineEnvironmentReloading) {
            environment.reload()
        }
    }.start()
}

start(await = false)、(起動後、blockしない)場合の実装例は以下を参考にしてください。

https://github.com/momosetkn/ktor-auto-reload-error-logging/blob/b4c1c852defe29bf1806c28c75db260f6dc15da6/src/main/kotlin/com/example/WorkaroundApplication.kt

最後にひとこと

エラーログが出ないエラーは、ネットでの検索のしようがないので、とりあえずなんらかのエラーログは出て欲しいです。今回は、ktorでreloadを行うコードを探してデバッガーでで内部の挙動を見て分かりました。

GitHubで編集を提案

Discussion