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).
Is your feature request related to a problem? Please describe.
The koin-ktor
Koinplugin installs a per-request scope hook insetupKoinScope:RequestScope.<init>generates a UUID viaKoinPlatformTools.generateId():On JVM,
generateId()resolves toUuid.random().toString(), which callsSecureRandom.nextBytes(...). With the default JCA provider on Linux (NativePRNG), this reads from/dev/urandomviaFileInputStream.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,setupKoinScopeis installed unconditionally.Reproduction (JDK 21, Linux/macOS):
ktor-server-nettykoin-ktor4.2.1BlockHound.install()before the engine startsStack trace:
Describe the solution you'd like
Avoid the SecureRandom-backed UUID per request when it isn't needed. Two complementary directions worth considering:
scoped { }definitions don't pay for the wiring at all.RequestScopeIDs 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
koin-ktorplugin and replacing it with a smallKoinPlatform.getKoin()-backed shim that mirrorsRoute.inject<T>()/Application.getKoin(). Works when noscoped { }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).