package org.jboss.pitbull.internal.nio.socket;
import org.jboss.pitbull.internal.logging.Logger;
import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.ArrayDeque;
import java.util.Iterator;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class Worker implements Runnable
{
interface Event
{
void execute();
}
protected Selector selector;
protected volatile boolean shutdown;
protected volatile boolean idle;
protected CountDownLatch shutdownLatch = new CountDownLatch(1);
protected Queue<FutureTask> eventQueue = new ArrayDeque<FutureTask>(10);
protected static final Logger logger = Logger.getLogger(Worker.class);
protected static final AtomicInteger counter = new AtomicInteger();
protected long numRegistered;
protected Thread workerThread;
public Worker() throws IOException
{
this.selector = Selector.open();
}
public long getNumRegistered()
{
return numRegistered;
}
public void clearMetrics()
{
numRegistered = 0;
}
public boolean inWorkerThread()
{
if (workerThread == null) return false;
return workerThread == Thread.currentThread();
}
public Future queueEvent(final Runnable runnable)
{
FutureTask futureTask = new FutureTask(runnable, null);
synchronized (eventQueue)
{
eventQueue.add(futureTask);
if (idle)
{
eventQueue.notify();
}
else
{
selector.wakeup();
}
}
return futureTask;
}
public void queueRegistration(final ManagedChannel channel)
{
Runnable runnable = new Runnable()
{
@Override
public void run()
{
executeRegistration(channel);
}
};
queueEvent(runnable);
}
protected void executeRegistration(ManagedChannel channel)
{
try
{
logger.trace("Registered channel.");
numRegistered++;
channel.getChannel().configureBlocking(false);
SelectionKey key = channel.getChannel().register(selector, SelectionKey.OP_READ);
channel.bindSelectionKey(this, key);
key.attach(channel);
}
catch (Exception e)
{
logger.error("Failed to execute socket registration", e);
try
{ channel.close(); }
catch (Exception ignored)
{}
}
}
protected void processReads()
{
Set<SelectionKey> selectedKeys = selector.selectedKeys();
for (Iterator<SelectionKey> i = selectedKeys.iterator(); i.hasNext(); )
{
SelectionKey key = i.next();
i.remove();
int readyOps = key.readyOps();
if ((readyOps & SelectionKey.OP_READ) != 0 || readyOps == 0)
{
ManagedChannel channel = (ManagedChannel) key.attachment();
try
{
channel.getHandler().handleRead(channel);
}
catch (Exception e)
{
logger.trace("Error reading channel: ", e);
channel.close();
}
if (!channel.getChannel().isOpen())
{
key.cancel();
}
}
}
}
public boolean isShutdown()
{
return shutdown;
}
public void shutdown()
{
synchronized (eventQueue)
{
shutdown = true;
eventQueue.notify();
}
selector.wakeup();
try
{
shutdownLatch.await();
}
catch (InterruptedException e)
{
throw new RuntimeException(e);
}
}
@Override
public void run()
{
String oldName = Thread.currentThread().getName();
Thread.currentThread().setName("Pitbull Worker Thread: " + counter.incrementAndGet());
workerThread = Thread.currentThread();
try
{
for (; ; )
{
if (shutdown) break;
try
{
SelectorUtil.select(selector);
if (shutdown) break;
synchronized (eventQueue)
{
// Empty all events
for (FutureTask event = eventQueue.poll(); event != null; event = eventQueue.poll())
{
event.run();
}
selector.selectNow(); // events may have changed selector status
processReads();
selector.selectNow(); // reads may have changed selector status
if (selector.keys().isEmpty())
{
// go to sleep if not managing any more connections
idle = true;
try
{
eventQueue.wait();
if (shutdown) break;
}
catch (InterruptedException e)
{
break;
}
idle = false;
}
}
}
catch (Throwable t)
{
logger.warn(
"Unexpected exception in the selector loop.", t);
// Prevent possible consecutive immediate failures that lead to
// excessive CPU consumption.
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
// Ignore.
}
}
}
}
finally
{
SelectorUtil.cleanupThreadSelector();
shutdownLatch.countDown();
Thread.currentThread().setName(oldName);
}
}
public void close()
{
try
{
for (FutureTask event = eventQueue.poll(); event != null; event = eventQueue.poll())
{
event.cancel(false);
}
}
catch (Exception ignore)
{
}
Set<SelectionKey> keys = selector.keys();
for (SelectionKey key : keys)
{
try
{
((ManagedChannel) key.attachment()).shutdown();
key.cancel();
}
catch (Exception ignored)
{
}
}
SelectorUtil.safeClose(selector);
}
}