package org.limewire.collection;
import java.util.LinkedList;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ThreadFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.limewire.concurrent.ExecutorsHelper;
/**
* Executes {@link Runnable Runnables} in a round-robin order per queue ID.
*
<pre>
class Runner implements Runnable {
final int item;
Runner(int item) {
this.item = item;
}
public void run() {
try {
Thread.sleep(150);
System.out.println("Item: " + item);
} catch (InterruptedException iex) {
System.out.println(iex.toString());
}
}
}
RRProcessingQueue rrpq = new RRProcessingQueue("sampleCodeRRProcessingQueue");
rrpq.execute(new Runner(1), "Abby");
rrpq.execute(new Runner(2), "Bob");
rrpq.execute(new Runner(3), "Abby");
rrpq.execute(new Runner(4), "Bob");
rrpq.execute(new Runner(5), "Chris");
try {
Thread.sleep(1000);
} catch (InterruptedException ignored) {}
Output:
Item: 1
Item: 2
Item: 5
Item: 3
Item: 4
</pre>
*/
/* TODO: Convert to using java.util.concurrent. */
public class RRProcessingQueue {
private static final Log LOG = LogFactory.getLog(RRProcessingQueue.class);
/** Factory to get new threads from. */
private final ThreadFactory FACTORY;
/** The thread doing the processing. */
private Thread _runner = null;
private final Map<Object, NamedQueue> queues = new HashMap<Object, NamedQueue>();
private final RoundRobinQueue<NamedQueue> lists = new RoundRobinQueue<NamedQueue>();
private int size;
public RRProcessingQueue(String name) {
FACTORY = ExecutorsHelper.daemonThreadFactory(name);
}
public synchronized void execute(Runnable runner, Object queueId) {
NamedQueue queue = queues.get(queueId);
if (queue == null) {
queue = new NamedQueue(new LinkedList<Runnable>(), queueId);
queues.put(queueId, queue);
lists.enqueue(queue);
}
queue.list.add(runner);
size++;
notifyAndStart();
}
/** Notifies the waiting thread or starts a new one. */
protected synchronized void notifyAndStart() {
notify();
if(_runner == null)
startRunner();
}
/** Starts a new runner. */
private synchronized void startRunner() {
_runner = FACTORY.newThread(new Processor());
_runner.setDaemon(true);
_runner.start();
}
protected synchronized boolean moreTasks() {
return size > 0;
}
protected synchronized Runnable next() {
Runnable ret = null;
while (lists.size() > 0) {
NamedQueue next = lists.next();
ret = next.next();
if (ret == null || next.list.isEmpty()) {
lists.removeAllOccurences(next);
queues.remove(next.name);
}
if (ret != null) {
size--;
return ret;
}
}
return null;
}
public synchronized int size() {
return size;
}
public synchronized void clear() {
if (LOG.isDebugEnabled())
LOG.debug("removing all "+size +" jobs from "+queues.size()+" queues");
queues.clear();
lists.clear();
size = 0;
}
public synchronized void clear(Object name) {
NamedQueue toRemove = queues.remove(name);
if (toRemove == null)
return;
lists.removeAllOccurences(toRemove);
if (LOG.isDebugEnabled())
LOG.debug("removing "+toRemove.list.size()+" jobs out of "+size);
size -= toRemove.list.size();
}
private static class NamedQueue {
final List<Runnable> list;
final Object name;
NamedQueue (List<Runnable> list, Object name) {
this.list = list;
this.name = name;
}
Runnable next() {
return list.isEmpty() ? null : list.remove(0);
}
}
/** The runnable that processes the queue. */
private class Processor implements Runnable {
public void run() {
try {
while(true) {
Runnable next = next();
if(next != null)
next.run();
// Ideally this would be in a finally clause -- but if it
// is then we can potentially ignore exceptions that were
// thrown.
synchronized(RRProcessingQueue.this) {
// If something was added before we grabbed the lock,
// process those items immediately instead of waiting
if(moreTasks())
continue;
// Wait a little bit to see if something new is going
// to come in, so we don't needlessly kill/recreate
// threads.
try {
RRProcessingQueue.this.wait(5 * 1000);
} catch(InterruptedException ignored) {}
// If something was added and notified us, process it
// instead of exiting.
if(moreTasks())
continue;
// Otherwise, exit
else
break;
}
}
} finally {
// We must restart a new runner if something was added.
// It's highly unlikely that something was added between
// the try of one synchronized block & the finally of another,
// but it technically is possible.
// We cannot loop here because we'd lose any exceptions
// that may have been thrown.
synchronized(RRProcessingQueue.this) {
if(moreTasks())
startRunner();
else
_runner = null;
}
}
}
}
}