[ 
https://issues.apache.org/jira/browse/LOG4J2-2990?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Diogo Monteiro closed LOG4J2-2990.
----------------------------------

> Make use of ThreadContext.putAll on SLF4j MDCAdapter
> ----------------------------------------------------
>
>                 Key: LOG4J2-2990
>                 URL: https://issues.apache.org/jira/browse/LOG4J2-2990
>             Project: Log4j 2
>          Issue Type: Improvement
>          Components: SLF4J Bridge
>    Affects Versions: 3.0.0, 2.14.0
>            Reporter: Diogo Monteiro
>            Priority: Minor
>             Fix For: 2.14.1
>
>
> Hi all.
> 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:
> {code:java}
> suspend onHandleRequest(request) {
>       MDC.setContextMap(<initial context>)
>       dbResponseAsync = async { callDatabase() }
>       service1ResponseAsync = async { callService1() }
>       dbResponse = dbResponseAsync.await()
>       service1Response  = service1ResponseAsync.await()
>      
>       (do something with both responses and log something here)
> }
> {code}
> 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 noticed that in the [Log4jMDCAdapter 
> implementation|https://github.com/apache/logging-log4j2/blob/master/log4j-slf4j-impl/src/main/java/org/apache/logging/slf4j/Log4jMDCAdapter.java#L58],
>  
> [ThreadContext.put|https://github.com/apache/logging-log4j2/blob/d9e01a981b860aeb8834eac3eed201c686884b7a/log4j-api/src/main/java/org/apache/logging/log4j/ThreadContext.java#L244]
>  is called for each entry of the map, creating {{N}} unmodifiable copies of 
> the context map for each entry.
> I think it's possible to just call {{ThreadContext.putAll}}, which internally 
> creates only a single copy for all entries. Something like:
> {code:java}
> // Some comments here
>     @Override
>     @SuppressWarnings("unchecked") // nothing we can do about this, 
> restricted by SLF4J API
>     public void setContextMap(@SuppressWarnings("rawtypes") final Map map) {
>         ThreadContext.clearMap();
>         ThreadContext.putAll((Map<String, String>) map);
>     }
> {code}
> 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. 
> Thank you.



--
This message was sent by Atlassian Jira
(v8.3.4#803005)

Reply via email to