I maintain a service that makes use of log4j2 with the SLF4j API, and, while load testing it, I noticed some GC pressure. Taking a closer look I could understand that one of the most allocated objects were maps (and their corresponding EntrySets and Entry), coming from the MDC implementation.
We make extensive use of Kotlin and the coroutines mechanism that the language provides. What happens is that a coroutine can suspend its execution to wait for a result, freeing the underlying thread to another coroutine. If we want to keep a MDC context that is coroutine-local (instead of thread local), the coroutines implementation call MDC.setContextMap and MDC.getContextMap on each suspension (and wake). So, in pseudo-code example:
The coroutine that executes onHandleRequest will suspend (and awake) two times in the await() function. This will call MDC.setContextMap three times for the same request: 1 time at the beginning, 1 time for each await().
I think it's possible to just call ThreadContext.putAll, which internally creates only a single copy for all entries. Something like:
In our use case this greatly reduces memory footprint and GC pressure, and reduces the time to call MDC.setContextMap to roughly half.
What do you think? Do you see any drawback?
Happy to create a PR for this in case you agree with this change.