package org.geoserver.monitor;
import java.util.ArrayList;
import java.util.Map;
import java.util.Queue;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.geotools.util.logging.Logging;
/**
* A task queue that groups tasks by key and ensures that tasks with same key
* execute serially.
*
* @author Justin Deoliveira, OpenGeo
*
* @param <K> The key type.
*/
public class PipeliningTaskQueue<K> implements Runnable {
static Logger LOGGER = Logging.getLogger("org.geoserver.monitor");
ConcurrentHashMap<K, Queue<Pipelineable<K>>> pipelines;
ScheduledExecutorService executor;
ExecutorService tasks;
public PipeliningTaskQueue() {
pipelines = new ConcurrentHashMap();
tasks = Executors.newCachedThreadPool();
}
public void start() {
executor = Executors.newScheduledThreadPool(4);
executor.scheduleAtFixedRate(this, 0, 10, TimeUnit.MILLISECONDS);
executor.scheduleAtFixedRate(this, 0, 10, TimeUnit.MILLISECONDS);
executor.scheduleAtFixedRate(this, 0, 10, TimeUnit.MILLISECONDS);
executor.scheduleAtFixedRate(this, 0, 10, TimeUnit.MILLISECONDS);
}
public void stop() {
executor.shutdown();
executor = null;
tasks.shutdown();
tasks = null;
}
public void execute(K key, Runnable task) {
execute(key, task, "");
}
public void execute(K key, Runnable task, String desc) {
Queue<Pipelineable<K>> pipeline = pipelines.get(key);
if (pipeline == null) {
synchronized (pipelines) {
pipeline = pipelines.get(key);
if (pipeline == null) {
pipeline = new ConcurrentLinkedQueue();
pipelines.put(key, pipeline);
}
}
}
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.finest("Queuing task into pipeline " + key);
}
pipeline.add(new Pipelineable<K>(key, task));
}
public void clear(K key) {
pipelines.remove(key);
}
public void shutdown() {
executor.shutdown();
tasks.shutdown();
}
public void run() {
for (Queue<Pipelineable<K>> pipeline : pipelines.values()) {
Pipelineable<K> job = pipeline.peek();
if (job != null) {
if (!job.lock.tryLock()) continue; //another thread already handling this job
if (job.future != null) {
//job has been submitted, if it is done remove it from
// the queue
if (job.future.isDone()) {
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.finest("Removing task from queue " + job.key);
}
pipeline.remove();
}
}
else {
//start the job
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.finest("Executing task in queue " + job.key);
}
job.future = tasks.submit(job.task);
}
job.lock.unlock();
}
}
}
public class Pipelineable<K> {
K key;
Runnable task;
Future<?> future;
Lock lock;
String desc;
public Pipelineable(K key, Runnable task) {
this.key = key;
this.task = task;
this.lock = new ReentrantLock();
}
}
public void print() {
for (Map.Entry<K, Queue<Pipelineable<K>>> e : pipelines.entrySet()) {
System.out.print(e.getKey());
for (Pipelineable<K> p : e.getValue()) {
System.out.print(p.desc + " ");
}
System.out.println();
}
}
}