package org.gbif.checklistbank.index.backfill;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class was implemented to be executed only one time, and probably be executed by a job scheduler (CRON for example).
* The behavior of the pool of threads can be customized using the properties file, using this file
* * is possible define the values for: poolSize, maxPoolSize and keepAliveTime; and additionally for the
* e-mail configuration (an email will be sent at the end of the process):
* smtp.server,email.to,email.from,email.subject,email.body,email.cc
* The thread pool is implemented using an {@link ExecutorCompletionService} and a {@link ThreadPoolExecutor} because
* some results can be return for each job: errors during the process or any other information.
*/
public abstract class ThreadPoolRunner<T> {
private static class DefaultRejectedExecutionHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable job, ThreadPoolExecutor executor) {
ThreadPoolRunner.LOG.debug("Job has been rejected by the executor, putting on queue");
try {
executor.getQueue().put(job);
} catch (InterruptedException e1) {
LOG.error("Work discarded, thread was interrupted while waiting for space to schedule: {}", job);
}
}
}
/**
* the number of threads to keep in the pool, even if they are idle.
*/
protected int poolSize = 0;
/**
* Executes each submitted task (agent synchronization) using one of possibly several pooled threads
*/
private ThreadPoolExecutor threadPool = null;
/**
* Decouples the production of new asynchronous tasks from the consumption of the results of completed tasks
*/
private CompletionService<T> completionService;
/**
* Queue of elements that will be executed by the {@link ThreadPoolExecutor}
*/
private final BlockingQueue<Runnable> queue = new SynchronousQueue<Runnable>();
/**
* Logger for the {@link ThreadPoolRunner} class
*/
private static final Logger LOG = LoggerFactory.getLogger(ThreadPoolRunner.class);
/**
* @param poolSize the number of threads to keep in the pool, even if they are idle.
*/
public ThreadPoolRunner(int poolSize) {
this.poolSize = poolSize;
}
protected abstract Callable<T> newJob();
/**
* Initialize the {@link ThreadPoolExecutor} and {@link ExecutorCompletionService}
*/
private void initThreadPool() {
threadPool = new ThreadPoolExecutor(
poolSize, poolSize, 0, TimeUnit.SECONDS, queue, new DefaultRejectedExecutionHandler()
);
completionService = new ExecutorCompletionService<T>(threadPool);
}
/**
* This method runs the synchronization for all the agent of the same type,
* creates a thread that will synchronized # of agents (defined by the agentsPerThread field)
*/
public int run() {
// localTasksCount contains the number of threads created, this count will be used for shutting down the service
initThreadPool();
int localTasksCount = 0;
try {
// gets the agents with service type: this.serviceTypeCode
Callable<T> job = newJob();
while (job != null) {
try {
LOG.debug("Submitting job {}, tasks count={}", job, localTasksCount);
completionService.submit(job);
localTasksCount += 1;
} catch (Exception e) {
LOG.error("Error when submiting job: {}", localTasksCount);
}
job = newJob();
}
LOG.info("All {} jobs submitted succesfully!", localTasksCount);
shutdownService(localTasksCount);
} catch (Exception e) {
LOG.error("Error when submiting jobs: {}", e);
}
return localTasksCount;
}
/**
* Performs the shutdown activities: waits each thread to complete the process and collects the results.
*/
protected void shutdownService(int tasksCount) {
try {
LOG.info("Initiating shutting down of the service");
while (tasksCount > 0) {
// waits for each task to return the results
Future<T> result = completionService.take();
tasksCount--;
try {
T taskResponse = result.get();
LOG.info("Job completed, {} still running", tasksCount);
taskResponseHook(taskResponse);
} catch (ExecutionException e) {
LOG.error("Error waiting a job to return results. {} still running jobs", tasksCount, e);
}
LOG.debug("Waiting for {} jobs to shutdown.", tasksCount);
}
List<Runnable> failedTasks = threadPool.shutdownNow();
LOG.debug("{} Tasks failed or forcibly removed from the thread pool", failedTasks.size());
} catch (Exception ignored) {
LOG.error("Thread pool prematurely terminated", ignored);
}
LOG.info("Shutdown complete");
}
/**
* Override this method if the main runner needs to consume the individual job results.
* @param taskResponse the finished job result
*/
protected void taskResponseHook (T taskResponse) {
}
}