/*
* Copyright 2013 MovingBlocks
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.terasology.utilities.concurrency;
import com.google.common.collect.Lists;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Comparator;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.TimeUnit;
/**
*/
public final class TaskMaster<T extends Task> {
private static final Logger logger = LoggerFactory.getLogger(TaskMaster.class);
private BlockingQueue<T> taskQueue;
private ExecutorService executorService;
private int threads;
private boolean running;
private String name;
private TaskMaster(String name, int threads, BlockingQueue<T> queue) {
this.name = name;
this.threads = threads;
if (threads <= 0) {
throw new IllegalArgumentException("Must have at least one thread.");
}
taskQueue = queue;
restart();
}
public static <T extends Task> TaskMaster<T> createFIFOTaskMaster(String name, int threads) {
return new TaskMaster<>(name, threads, new LinkedBlockingQueue<>());
}
public static <T extends Task & Comparable<? super T>> TaskMaster<T> createPriorityTaskMaster(String name, int threads, int queueSize) {
return new TaskMaster<>(name, threads, new PriorityBlockingQueue<>(queueSize));
}
public static <T extends Task> TaskMaster<T> createPriorityTaskMaster(String name, int threads, int queueSize, Comparator<T> comparator) {
return new TaskMaster<>(name, threads, new PriorityBlockingQueue<>(queueSize, comparator));
}
public static <T extends Task> TaskMaster<T> createDynamicPriorityTaskMaster(String name, int threads, Comparator<T> comparator) {
return new TaskMaster<>(name, threads, new DynamicPriorityBlockingQueue<>(comparator));
}
/**
* Offers a task to this task master. This does not block, but may fail if the queue is full.
*
* @param task
* @return Whether the task was successfully added to the queue.
*/
public boolean offer(T task) {
return taskQueue.offer(task);
}
/**
* Adds a task to this task master. This blocks until the task can be added if the queue is full.
*
* @param task
*/
public void put(T task) throws InterruptedException {
taskQueue.put(task);
}
public void shutdown(T shutdownTask, boolean awaitComplete) {
if (!shutdownTask.isTerminateSignal()) {
throw new IllegalArgumentException("Expected task to provide terminate signal");
}
if (!awaitComplete) {
taskQueue.drainTo(Lists.newArrayList());
}
for (int i = 0; i < threads; ++i) {
try {
taskQueue.offer(shutdownTask, 250, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
logger.error("Failed to enqueue shutdown request", e);
}
}
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
executorService.shutdown();
try {
if (!executorService.awaitTermination(20, TimeUnit.SECONDS)) {
logger.warn("Timed out awaiting thread termination");
executorService.shutdownNow();
}
} catch (InterruptedException e) {
logger.warn("Interrupted awaiting chunk thread termination");
executorService.shutdownNow();
}
return null;
});
running = false;
}
public void restart() {
if (!running) {
executorService = Executors.newFixedThreadPool(threads);
for (int i = 0; i < threads; ++i) {
executorService.execute(new TaskProcessor<>(name + "-" + i, taskQueue));
}
running = true;
}
}
}