/**
* Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.util;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.slf4j.MDC;
/**
* An extension of ThreadPoolExecutor that is aware of slf4j mapped diagnostic
* logging (MDC). MDC allows per-thread, contextual information to be output
* to logging files. This is a map that could hold a session id for a web
* application or an identifier for a request from an external service. MDC
* information is passed down to child threads when they are created but falls
* down at the point where a thread-pool is used. In these cases no context
* information is passed on and the information will be absent from the logs.
*
* This executor ensures that MDC information is copied from the calling
* thread onto the worker thread and removed once the worker has completed
* and is returned to the pool.
*
* Because access to the MDC is via static methods, it is only possible
* to access the context applicable to the current thread i.e. when submitting
* a job to the executor we have access to the calling thread's MDC, but no way
* of accessing the worker's. Similarly, when the worker executes the job, we
* have access to the worker's MDC but not the original caller's. To handle
* this, the class records the MDC information from a thread when it submits
* a job. When the job is executed, the MDC information is looked up and
* copied to the worker.
*
* Note that this class overrides {@link #execute(Runnable)} which means
* there is no need to override the rest of the methods defined in
* {@link ExecutorService}
*/
public class MdcAwareThreadPoolExecutor extends ThreadPoolExecutor {
/**
* Map storing the jobs to be executed and the MDC information which was
* held by the calling thread when the job was submitted. Note that,
* depending on which method is used to submit a job, the key may not
* be the actual job submitted (e.g. if a Callable is submitted, it
* gets converted by the executor).
*/
private final ConcurrentMap<Runnable, Map<String, String>> _diagnosticContexts = new ConcurrentHashMap<>();
/**
* Create a new instance with the specified thread factory. Note that
* this will create an executor with the same defaults as if calling
* {@link Executors#newCachedThreadPool(ThreadFactory)}
*
* @param factory the factory to use when the executor creates a new thread
*/
public MdcAwareThreadPoolExecutor(ThreadFactory factory) {
super(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), factory);
}
/**
* Create a new instance.
*
* @param coreThreads the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maxThreads the maximum number of threads to allow in the pool
* @param keepAliveTime when the number of threads is greater than the
* core, this is the maximum time that excess idle threads will wait for
* new tasks before terminating.
* @param timeUnit the time unit for the {@code keepAliveTime} argument
* @param queue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable} tasks submitted
* by the {@code execute} method.
* @param factory the factory to use when the executor creates a new thread
*/
public MdcAwareThreadPoolExecutor(int coreThreads,
int maxThreads,
long keepAliveTime,
TimeUnit timeUnit,
BlockingQueue<Runnable> queue,
ThreadFactory factory) {
super(coreThreads, maxThreads, keepAliveTime, timeUnit, queue, factory);
}
@Override
protected void beforeExecute(Thread t, Runnable task) {
// This method is called by the worker thread before it executes the
// specified task. Therefore we can insert the MDC information and it will
// be available as the task is executed
Map<String, String> contextMap = _diagnosticContexts.get(task);
if (contextMap != null) {
MDC.setContextMap(contextMap);
}
// As per documentation on {@link ThreadPoolExecutor#beforeExecute}, we should
// call the super at the end of the method
super.beforeExecute(t, task);
}
@Override
protected void afterExecute(Runnable task, Throwable t) {
// This method is called by the worker thread after it has executed the
// specified task.
// As per documentation on {@link ThreadPoolExecutor#afterExecute}, we should
// call the super at the start of the method
super.afterExecute(task, t);
_diagnosticContexts.remove(task);
// Clear the MDC information from the worker thread
MDC.clear();
}
@Override
public void execute(Runnable command) {
recordDiagnosticContext(command);
super.execute(command);
}
@Override
public boolean remove(Runnable task) {
boolean success = super.remove(task);
// If remove fails then task has already been started and the
// job will be removed when the task completes
if (success) {
_diagnosticContexts.remove(task);
}
return success;
}
@SuppressWarnings("unchecked")
private void recordDiagnosticContext(Runnable task) {
Map<String, String> contextMap = MDC.getCopyOfContextMap();
if (contextMap != null) {
_diagnosticContexts.put(task, contextMap);
}
}
}