Copying MDC through Custom Thread Pool Executor

The Unknown
2 min readJun 12, 2021

MDC (Mapped Diagnostic Context) is used to set contextual information about the HTTP request like request id, user id etc., and can be made available on all log statements through Pattern Layout. This will liberate developers from handling contextual information at every log statement.

MDC in Multi-Threading

As this contextual information is maintained at a thread level, when a thread switch happens the new thread will not have this contextual information. The contextual information need to be copied to the new thread to avoid missing information like request id, user id etc., in log statements.

Copying the Context Information

Executor Service is on of the java tools to create a thread pool and execute tasks in the thread pool. Without a custom executor, copying MDC context would like below

ExecutorService executorService = Executors.newFixedThreadPool(10);
final Map<String, String> contextMap = MDC.getCopyOfContextMap();
executorService.execute(new Runnable() {
public void run() {
System.out.println("Copying context");
MDC.setContextMap(contextMap);
// Do Task related stuff
}
});
executorService.execute(new Runnable() {
public void run() {
System.out.println("Copying context");
MDC.setContextMap(contextMap);
// Do Task related stuff
}
});

Concerns

  • The developers need to be aware of copying the context every time a thread switch happens.
  • The same copy statement is repeated at multiple places deviating from the common software principles like DRY

Custom Thread Pool Executor

Using custom thread pool executor we can offload the MDC copying responsibility to a single class which deals with this. Below is an example implementation of custom thread pool executor.

public class CustomThreadPoolExecutor extends ThreadPoolExecutor {
public CustomThreadPoolExecutor(
final int corePoolSize,
final int maximumPoolSize,
final long keepAliveTime,
final TimeUnit unit,
final BlockingQueue<Runnable> workQueue
) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
@Override
public void execute(final Runnable command) {
super.execute(wrapWithContext(command));
}

private Runnable wrapWithContext(final Runnable task) {
Map<String, String> contextMap = MDC.getCopyOfContextMap();
return () -> {
MDC.setContextMap(contextMap);
task.run();
};
}
}

Below is a wrapper factory class to create instances of CustomThreadPoolExecutor

public final class CustomExecutors {
private CustomExecutors() {
}
public static ExecutorService newFixedThreadPool(final int nThreads) {
return new CustomThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
}

Using custom executor to execute tasks

ExecutorService executorService = CustomExecutors.newFixedThreadPool(10);
executorService.execute(new Runnable() {
public void run() {
// Do Task related stuff
}
});
executorService.execute(new Runnable() {
public void run() {
// Do Task related stuff
}
});

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

No responses yet

Write a response