Skip to content

kuri-bind: unbounded reflective plan/scan caches pin ClassLoaders for the process lifetime #89

Description

@OmarAlJarrah

Problem

PlanCompiler.plans (kuri-bind/src/jvmMain/kotlin/org/dexpace/kuri/bind/internal/PlanCompiler.kt:39) and KotlinReflectMemberScanner.scanCache (kuri-bind/src/jvmMain/kotlin/org/dexpace/kuri/bind/internal/MemberScanner.kt:81) are plain ConcurrentHashMap<KClass<*>, ...> with no size bound, TTL, or weak/soft keys. Both live inside the process-lifetime KuriBind.executor singleton (kuri-bind/src/jvmMain/kotlin/org/dexpace/kuri/bind/KuriBind.kt:50).

A KClass<*> strongly retains its backing java.lang.Class, which in turn strongly retains its defining ClassLoader. Caching plans/scans keyed by every distinct KClass seen means every distinct class that has ever been bound is retained — and with it, its ClassLoader — for as long as the process runs.

The leak surface is broader than just the annotated request/response types passed at the API boundary: runtimeValueIsBindable routes the runtime class of arbitrary nested values through planFor, so nested value types also get cached.

Impact

In a long-running host that creates classes dynamically per unit of work — an application server with per-deployment ClassLoaders, a scripting engine, bytecode-generated proxies, hot code redeploy — binding a stream of distinct generated classes accumulates one plan+scan entry per Class forever. The strong Class keys prevent those ClassLoaders from ever being garbage collected, leaking metaspace and ClassLoader instances for the life of the process.

Suggested fix

Bound the caches (e.g. an LRU with a fixed max size) or use weak keys (WeakHashMap/Caffeine-style weak-key cache) so a KClass that's no longer referenced elsewhere can be collected along with its ClassLoader.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions