Skip to content

RequestScope UUID blocks Netty event loop on every request #2410

Description

@lfavreli-betclic

Note: this isn't a KFIP-scale design, it's a small behavioral change to existing per-request scope wiring.

Is your feature request related to a problem? Please describe.

The koin-ktor Koin plugin installs a per-request scope hook in setupKoinScope:

// org/koin/ktor/plugin/KoinPlugin.kt (4.2.1)
on(CallSetup) { call ->
    val scopeComponent = RequestScope(koinApplication.koin, call)
    call.attributes.put(KOIN_SCOPE_ATTRIBUTE_KEY, scopeComponent.scope)
}

RequestScope.<init> generates a UUID via KoinPlatformTools.generateId():

// org/koin/ktor/plugin/RequestScope.kt (4.2.1)
class RequestScope(private val _koin: Koin, call: ApplicationCall) : KoinScopeComponent {
    private val scopeId = "request_" + KoinPlatformTools.generateId()
    override val scope = createScope(scopeId = scopeId, source = call)
}

On JVM, generateId() resolves to Uuid.random().toString(), which calls SecureRandom.nextBytes(...). With the default JCA provider on Linux (NativePRNG), this reads from /dev/urandom via FileInputStream.readBytes.

When the Ktor engine is Netty, the read lands on a Netty event-loop thread, which BlockHound surfaces as an event-loop blocking violation.

This happens on every HTTP request, even when the application declares no scoped { } definitions, setupKoinScope is installed unconditionally.

Reproduction (JDK 21, Linux/macOS):

  • Ktor 3.4.0 + ktor-server-netty
  • koin-ktor 4.2.1
  • BlockHound.install() before the engine starts
  • Send GET requests to any route

Stack trace:

reactor.blockhound.BlockingOperationError: Blocking call! java.io.FileInputStream#readBytes
    at java.base/sun.security.provider.NativePRNG$RandomIO.readFully
    at java.base/java.security.SecureRandom.nextBytes
    at kotlin.uuid.UuidKt__UuidJVMKt.secureRandomBytes(UuidJVM.kt:20)
    at kotlin.uuid.Uuid$Companion.random(Uuid.kt:582)
    at org.koin.mp.KoinPlatformTools_jvmKt.generateId(KoinPlatformTools.jvm.kt:7)
    at org.koin.ktor.plugin.RequestScope.<init>(RequestScope.kt:31)
    at org.koin.ktor.plugin.KoinPluginKt$setupKoinScope$1.invokeSuspend(KoinPlugin.kt:97)
    ...
    at io.ktor.server.netty.NettyApplicationCallHandler.handleRequest(NettyApplicationCallHandler.kt:55)
    at io.netty.channel.SingleThreadIoEventLoop.run

Describe the solution you'd like

Avoid the SecureRandom-backed UUID per request when it isn't needed. Two complementary directions worth considering:

  • Make per-request scope creation opt-in or lazy, so applications that don't use scoped { } definitions don't pay for the wiring at all.
  • Reconsider whether RequestScope IDs need to be cryptographically random. They only need uniqueness within a process, so a non-blocking source would be enough.

Describe alternatives you've considered

  • Dropping the koin-ktor plugin and replacing it with a small KoinPlatform.getKoin()-backed shim that mirrors Route.inject<T>() / Application.getKoin(). Works when no scoped { } definitions are used, but discards the per-request scope feature entirely, not a viable option for users who rely on it.

Target Koin project

koin-ktor (verified on 4.2.1).

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions