Describe the bug
I identified two critical issues in LoggingContext.java related to thread context management:
-
Memory Leak: The static THREAD_IDS set (Line 49) accumulates Thread IDs in createContext but never removes them. In a long-running application with dynamic thread creation, this set will grow indefinitely, leading to a heap memory leak.
-
Thread Pollution: The dispose() method (Line 97) only polls the internal Deque but never calls CONTEXT_THREAD_LOCAL.remove(). Since InheritableThreadLocal is used (Line 47), the reference remains attached to the thread even after "disposal". In thread pool scenarios, this causes context pollution (crosstalk) where subsequent tasks inherit stale logging contexts.
Steps to Reproduce
-
Call LoggingContext.init(...) in a thread.
-
Call LoggingContext.dispose().
-
Inspect the static LoggingContext.THREAD_IDS set — the thread ID is still present.
-
(For pollution) Reuse the thread for a new task without explicitly initializing a new context. Call LoggingContext.context(), and it may retrieve the stale InheritableThreadLocal value from the previous usage.
Expected behavior
-
dispose() should verify if the context stack is empty and call CONTEXT_THREAD_LOCAL.remove() to strictly clean up the thread.
-
THREAD_IDS should either be removed (if not strictly necessary) or paired with a removal mechanism to prevent unbounded growth (OOM risk).
Actual behavior
-
THREAD_IDS grows indefinitely.
-
InheritableThreadLocal variables persist on threads after dispose() is called, causing potential logic errors in thread pools.
Dependency versions
Main master
Source file analysis based on: com.epam.reportportal.service.LoggingContext
Additional context
Reference Code:
// LoggingContext.java
private static final Set THREAD_IDS = Collections.newSetFromMap(new ConcurrentHashMap<>()); // Never cleared
public static LoggingContext dispose() {
// Only polls the deque, missing CONTEXT_THREAD_LOCAL.remove()
return ofNullable(getContext()).map(Deque::poll).orElse(null);
}
Describe the bug
I identified two critical issues in LoggingContext.java related to thread context management:
Memory Leak: The static THREAD_IDS set (Line 49) accumulates Thread IDs in createContext but never removes them. In a long-running application with dynamic thread creation, this set will grow indefinitely, leading to a heap memory leak.
Thread Pollution: The dispose() method (Line 97) only polls the internal Deque but never calls CONTEXT_THREAD_LOCAL.remove(). Since InheritableThreadLocal is used (Line 47), the reference remains attached to the thread even after "disposal". In thread pool scenarios, this causes context pollution (crosstalk) where subsequent tasks inherit stale logging contexts.
Steps to Reproduce
Call LoggingContext.init(...) in a thread.
Call LoggingContext.dispose().
Inspect the static LoggingContext.THREAD_IDS set — the thread ID is still present.
(For pollution) Reuse the thread for a new task without explicitly initializing a new context. Call LoggingContext.context(), and it may retrieve the stale InheritableThreadLocal value from the previous usage.
Expected behavior
dispose() should verify if the context stack is empty and call CONTEXT_THREAD_LOCAL.remove() to strictly clean up the thread.
THREAD_IDS should either be removed (if not strictly necessary) or paired with a removal mechanism to prevent unbounded growth (OOM risk).
Actual behavior
THREAD_IDS grows indefinitely.
InheritableThreadLocal variables persist on threads after dispose() is called, causing potential logic errors in thread pools.
Dependency versions
Main master
Source file analysis based on: com.epam.reportportal.service.LoggingContext
Additional context
Reference Code:
// LoggingContext.java
private static final Set THREAD_IDS = Collections.newSetFromMap(new ConcurrentHashMap<>()); // Never cleared
public static LoggingContext dispose() {
// Only polls the deque, missing CONTEXT_THREAD_LOCAL.remove()
return ofNullable(getContext()).map(Deque::poll).orElse(null);
}