package org.ovirt.engine.core.utils.threadpool;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.ovirt.engine.core.common.config.Config;
import org.ovirt.engine.core.common.config.ConfigValues;
import org.ovirt.engine.core.utils.CorrelationIdTracker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ThreadPoolUtil {
private static final Logger log = LoggerFactory.getLogger(ThreadPoolUtil.class);
private static class InternalThreadExecutor extends ThreadPoolExecutor {
/**
* The pool which will be created with corePoolSize equal to ConfigValues.DefaultMinThreadPoolSize
* maximumPoolSize equal to DefaultMaxThreadPoolSize
*/
public InternalThreadExecutor() {
super(Config.<Integer> getValue(ConfigValues.DefaultMinThreadPoolSize),
Config.<Integer> getValue(ConfigValues.DefaultMaxThreadPoolSize),
60L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(Config.<Integer>getValue(ConfigValues.DefaultMaxThreadWaitQueueSize)),
new ThreadPoolExecutor.CallerRunsPolicy());
}
@Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
String threadName = t.getName();
if (!threadName.startsWith("org.ovirt.thread.")) {
t.setName("org.ovirt.thread." + threadName);
}
if (getQueue().size() > 5) {
log.warn("Executing a command '{}', but note that there are {} tasks in the queue.",
r.getClass().getName(),
getQueue().size());
}
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
if (t != null) {
log.error("Execution of task failed", t);
}
super.afterExecute(r, t);
CorrelationIdTracker.clean();
}
}
private static class InternalWrapperRunnable implements Runnable {
private Runnable job;
/**
* Identifies the correlation-id associated with the thread invoker
*/
private String correlationId;
public InternalWrapperRunnable(Runnable job, String correlationId) {
this.job = job;
this.correlationId = correlationId;
}
@Override
public void run() {
CorrelationIdTracker.setCorrelationId(correlationId);
job.run();
}
}
private static class InternalCallable<V> implements Callable<V> {
private Callable<V> job;
/**
* Identifies the correlation-id associated with the thread invoker
*/
private String correlationId;
public InternalCallable(Callable<V> job) {
this.job = job;
this.correlationId = CorrelationIdTracker.getCorrelationId();
}
@Override
public V call() throws Exception {
CorrelationIdTracker.setCorrelationId(correlationId);
return job.call();
}
}
private static final ExecutorService es = new InternalThreadExecutor();
/**
* Creates a completion service to allow launching of tasks (callable objects)
* concurrently, and use a blocking queue like interface to get the tasks
* execution results
*/
public static <V> ExecutorCompletionService<V> createCompletionService() {
return new ExecutorCompletionService<>(es);
}
private static <T> List<Callable<T>> buildSessionTasks(Collection<? extends Callable<T>> tasks) {
List<Callable<T>> sessionedTask = new ArrayList<>();
for (Callable<T> task : tasks) {
sessionedTask.add(new InternalCallable<>(task));
}
return sessionedTask;
}
/**
* Creates a completion service to allow launching of tasks (callable objects)
* concurrently, and use a blocking queue like interface to get the tasks
* @param tasks collection of tasks (callable objects) to submit
*/
public static <V> ExecutorCompletionService<V> createCompletionService(Iterable<Callable<V>> tasks) {
ExecutorCompletionService<V> ecs = createCompletionService();
submitTasks(ecs, tasks);
return ecs;
}
public static <V> List<Future<V>> submitTasks(ExecutorCompletionService<V> ecs, Iterable<Callable<V>> tasks) {
List<Future<V>> futures = new LinkedList<>();
if (tasks != null) {
for (Callable<V> callable : tasks) {
futures.add(ecs.submit(callable));
}
}
return futures;
}
public static void execute(Runnable command) {
try {
es.submit(new InternalWrapperRunnable(command,
CorrelationIdTracker.getCorrelationId()));
} catch (RejectedExecutionException e) {
log.warn("The thread pool is out of limit. A submitted task was rejected");
throw e;
}
}
@SuppressWarnings("unchecked")
public static <V> Future<V> execute(FutureTask<V> command) {
try {
return (Future<V>) es.submit(command);
} catch (RejectedExecutionException e) {
log.warn("The thread pool is out of limit. The submitted event was rejected");
throw e;
}
}
/**
* Executes the given tasks, returning a list of results
* when all complete, in case of empty or null list a null will be return
*/
public static <T> List<T> invokeAll(Collection<? extends Callable<T>> tasks) {
if (tasks != null && !tasks.isEmpty()) {
try {
List<Callable<T>> sessionedTask = buildSessionTasks(tasks);
List<Future<T>> resultFutureList = es.invokeAll(sessionedTask);
List<T> resultList = new ArrayList<>();
for (Future<T> future : resultFutureList) {
resultList.add(future.get());
}
return resultList;
} catch (Exception e) {
log.warn("The thread pool failed to execute list of tasks: {}", e.getMessage());
log.debug("Exception", e);
throw new RuntimeException(e);
}
}
return null;
}
}