Crazy Dino
Crazy Dino

Reputation: 848

SLF4J MDC Memory Leak

I've had a google for this, looked at multiple suggestions and nothing seems to help.

I have a JAX-RS application which using MDC, when an endpoint is hit sets a transactionId in order to make debugging easier. However, when i stop or restart Tomcat the logs are filled with entries like this:

27-Sep-2014 09:42:14.858 SEVERE [localhost-startStop-2] org.apache.catalina.loader.WebappClassLoader.checkThreadLocalMapForLeaks The web application [/core-1.0.0-RC2] created a ThreadLocal with key of type [org.apache.log4j.helpers.ThreadLocalMap] (value [org.apache.log4j.helpers.ThreadLocalMap@464437fc]) and a value of type [java.util.Hashtable] (value [{siteCode=000tst, transactionId=dc8f3a1b-1d7a-4f91-abf6-58d015632d03}]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak.

I have a RequestFilter where MDC is called:

import org.slf4j.MDC;

import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import java.io.IOException;
import java.util.UUID;

public void filter(ContainerRequestContext containerRequestContext) throws IOException {

    String siteCodeHeader = containerRequestContext.getHeaderString("Site-Code");

    if (siteCodeHeader != null) {
        MDC.put("siteCode", siteCodeHeader);
    } else {
        MDC.put("siteCode", "NULL");
    }
    MDC.put("transactionId", UUID.randomUUID().toString());


}

These are my sl4fj dependencies:

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.7</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.7</version>
</dependency>

If I have a ResponseFilter with MDC.clear() It removes the values from the MDC, but doesn't seem to clear the thread:

27-Sep-2014 09:12:58.216 SEVERE [localhost-startStop-2] org.apache.catalina.loader.WebappClassLoader.checkThreadLocalMapForLeaks The web application [/core-1.0.0-RC2] created a ThreadLocal with key of type [org.apache.log4j.helpers.ThreadLocalMap] (value [org.apache.log4j.helpers.ThreadLocalMap@391216c7]) and a value of type [java.util.Hashtable] (value [{}]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak.

Apparently it was fixed in log4j 1.2.17 but the changes don't seem to have filtered through to slf4j.

Upvotes: 3

Views: 6540

Answers (3)

Crazy Dino
Crazy Dino

Reputation: 848

Using the mix of the previous two answers I was able to fix the problem.

I used the log4j implementation of MDC rather than SLF4J, and added a ResponseFilter as well to do the clear out. It may, or may not have affected it, but I also used provider annotations rather than stipulating the classes in the web.xml.

RequestFilter (much the same):

package com.example.jaxrs;

import org.apache.log4j.MDC;

import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.ext.Provider;
import java.io.IOException;
import java.util.UUID;

@Provider
public class TransactionIdentifierRequestFilter implements ContainerRequestFilter {

    @Override
    public void filter(ContainerRequestContext containerRequestContext) throws IOException {

        String siteCodeHeader = containerRequestContext.getHeaderString("Site-Code");

        if (siteCodeHeader != null) {
            MDC.put("siteCode", siteCodeHeader);
        } else {
            MDC.put("siteCode", "NULL");
        }
        MDC.put("transactionId", UUID.randomUUID().toString());

    }
}

ResponseFilter:

package com.example.jaxrs;

import org.apache.log4j.MDC;

import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.ext.Provider;
import java.io.IOException;

@Provider
public class TransactionIdentifierResponseFilter implements ContainerResponseFilter {

    @Override
    public void filter(ContainerRequestContext containerRequestContext, ContainerResponseContext containerResponseContext) throws IOException {
        MDC.clear();
    }
}

web.xml

<init-param>
    <param-name>jersey.config.server.provider.packages</param-name>
    <param-value>com.example.jaxrs</param-value>
</init-param>

Upvotes: 2

Hannes
Hannes

Reputation: 2073

MDCs are hold in the current thread using ThreadLocal. If you add values in a filter, you have to remove them after the service call.

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
   try
   {

     //add your mdcs

     // proceed along the chain
     chain.doFilter(request, response);

  } 
  finally
  {

     //remove your mdcs

  }
}

Upvotes: 1

Michael-O
Michael-O

Reputation: 18405

This is clearly not a SLF4J problem but a Log4J problem only. Add the fixed Log4J version as a direct dependency to your POM with runtime scope and your problem should go away.

Upvotes: 0

Related Questions