-
Notifications
You must be signed in to change notification settings - Fork 637
Description
Bug Description
The RedisStateMachineContextRepository class uses a private static final ThreadLocal to cache Kryo instances. However, the code never calls kryoThreadLocal.remove() after using the instance.
In a managed environment (e.g., Tomcat/Jetty) where threads are pooled and reused, this uncleaned ThreadLocal creates a strong reference chain: Thread -> ThreadLocalMap -> Kryo -> Registered Class -> ClassLoader.
This prevents the ClassLoader from being garbage collected during application redeployment, leading to Metaspace OutOfMemoryError.
Code Analysis
In src/main/java/org/springframework/statemachine/data/redis/RedisStateMachineContextRepository.java:
-
Static ThreadLocal Definition (Line 47):
private static final ThreadLocal kryoThreadLocal = new ThreadLocal() { ... }; -
Usage without Cleanup (Line 90 & 103): In methods serialize and deserialize:
Kryo kryo = kryoThreadLocal.get();
// Use kryo...
// MISSING: kryoThreadLocal.remove();
Impact
Context Pollution: Kryo instances may retain references to old objects or configurations from previous executions on the same thread.
Memory Leak: Severe ClassLoader leaks in web containers, eventually crashing the JVM with OOM.
Suggested Fix
Instead of a raw ThreadLocal, use the KryoPool mechanism provided by Kryo, or ensure remove() is called in a finally block.
Recommended Approach (KryoPool):
// Use com.esotericsoftware.kryo.pool.KryoPool instead of ThreadLocal
private final KryoPool pool = new KryoPool.Builder(factory).softReferences().build();
private byte[] serialize(StateMachineContext<S, E> context) {
return pool.run(kryo -> {
// serialization logic
});
}