package com.limegroup.gnutella.io;
import java.io.IOException;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.SocketChannel;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.LinkedList;
import com.util.LOG;
import com.limegroup.gnutella.ErrorService;
import com.limegroup.gnutella.util.ManagedThread;
/**
* Dispatcher for NIO.
*/
public class NIODispatcher implements Runnable {
private static final NIODispatcher INSTANCE = new NIODispatcher();
public static final NIODispatcher instance() { return INSTANCE; }
/**
* Constructs the sole NIODispatcher, starting its thread.
*/
private NIODispatcher() {
try {
selector = Selector.open();
} catch(IOException iox) {
throw new RuntimeException(iox);
}
Thread t = new ManagedThread(this, "NIODispatcher");
t.start();
}
/** The selector this uses. */
private Selector selector = null;
/** Pending queue. */
private final List PENDING = new LinkedList();
/** Register interest in accepting */
public void registerAccept(SelectableChannel channel, NIOHandler attachment) {
register(channel, attachment, SelectionKey.OP_ACCEPT);
}
/** Register interest in connecting */
public void registerConnect(SelectableChannel channel, NIOHandler attachment) {
register(channel, attachment, SelectionKey.OP_CONNECT);
}
/** Register interest in reading */
public void registerRead(SelectableChannel channel, NIOHandler attachment) {
register(channel, attachment, SelectionKey.OP_READ);
}
/** Register interest in writing */
public void registerWrite(SelectableChannel channel, NIOHandler attachment) {
register(channel, attachment, SelectionKey.OP_WRITE);
}
/** Register interest in both reading & writing */
public void registerReadWrite(SelectableChannel channel, NIOHandler attachment) {
register(channel, attachment, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
}
/** Register interest */
private void register(SelectableChannel channel, NIOHandler attachment, int op) {
synchronized(PENDING) {
PENDING.add(new PendingOp(channel, attachment, op));
}
// Technically, it is possible (and recommended) to do a selector.wakeup() here,
// and have selector.select() without a timeout. Unfortunately, due to bugs
// with Selector on various OS's, specifically bugs with wakeup() causing
// select() to always return immediately forever, this isn't possible.
}
/** Registers a SelectableChannel as being interested in a write again. */
public void interestWrite(SelectableChannel channel, boolean on) {
interest(channel, SelectionKey.OP_WRITE, on);
}
/** Registers a SelectableChannel as being interested in a read again. */
public void interestRead(SelectableChannel channel, boolean on) {
interest(channel, SelectionKey.OP_READ, on);
}
/** Registers interest on the channel for the given op */
private void interest(SelectableChannel channel, int op, boolean on) {
try {
SelectionKey sk = channel.keyFor(selector);
if(sk != null && sk.isValid()) {
synchronized(channel.blockingLock()) {
if(on)
sk.interestOps(sk.interestOps() | op);
else
sk.interestOps(sk.interestOps() & ~op);
}
}
} catch(CancelledKeyException cke) {
// It is possible to register interest on any thread, which means
// that the key could have been cancelled at any time.
// Despite checking for isValid above, it may become invalid.
// It's a harmless exception, so ignore it.
}
}
/**
* Cancel SelectionKey, close Channel and "free" the attachment
*/
private void cancel(SelectionKey sk, NIOHandler handler) {
sk.cancel();
SelectableChannel channel = (SelectableChannel)sk.channel();
try {
channel.close();
} catch (IOException err) {
LOG.error("Channel.close()", err);
handler.handleIOException(err);
}
}
/**
* Accept an icoming connection
*
* @throws IOException
*/
private void processAccept(SelectionKey sk, AcceptHandler handler) throws IOException {
if(LOG.isDebugEnabled())
LOG.debug("Handling accept: " + handler);
if (!sk.isValid())
return;
ServerSocketChannel ssc = (ServerSocketChannel)sk.channel();
SocketChannel channel = ssc.accept();
if (channel == null)
return;
if (channel.isOpen()) {
channel.configureBlocking(false);
handler.handleAccept(channel);
} else {
try {
channel.close();
} catch (IOException err) {
LOG.error("SocketChannel.close()", err);
}
}
}
/**
* Process a connected channel.
*/
private void processConnect(SelectionKey sk, ConnectHandler handler) throws IOException {
if(LOG.isDebugEnabled())
LOG.debug("Handling connect: " + handler);
if(!sk.isValid())
return;
SocketChannel channel = (SocketChannel)sk.channel();
boolean finished = channel.finishConnect();
if(finished) {
sk.interestOps(0); // interested in nothing just yet.
handler.handleConnect();
} else
cancel(sk, handler);
}
/**
* Read data
*
* @throws IOException
*/
private void processRead(SelectionKey sk, ReadHandler handler) throws IOException {
if(LOG.isDebugEnabled())
LOG.debug("Handling read: " + handler);
if (!sk.isValid())
return;
handler.handleRead();
}
/**
* Write data
*
* @throws IOException
*/
private void processWrite(SelectionKey sk, WriteHandler handler) throws IOException {
if(LOG.isDebugEnabled())
LOG.debug("Handling write: " + handler);
if (!sk.isValid())
return;
handler.handleWrite();
}
/**
* Adds any pending registrations.
*/
private void addPendingItems() {
synchronized(PENDING) {
for(Iterator i = PENDING.iterator(); i.hasNext(); ) {
PendingOp next = (PendingOp)i.next();
try {
next.channel.register(selector, next.op, next.handler);
} catch(IOException iox) {
next.handler.handleIOException(iox);
}
}
PENDING.clear();
}
}
/**
* The actual NIO run loop
*/
private void process() {
while(true) {
// This sleep is technically not necessary, however occasionally selector
// begins to wakeup with nothing selected. This happens very frequently on Linux,
// and sometimes on Windows (bugs, etc..). The sleep prevents busy-looping.
// It also allows pending registrations & network events to queue up so that
// selection can handle more things in one round.
// This is unrelated to the wakeup()-causing-busy-looping. There's other bugs
// that cause this.
try {
Thread.sleep(50);
} catch(InterruptedException ix) {
LOG.warn("Selector interrupted", ix);
}
addPendingItems();
try {
// see register(...) for why this has a timeout
selector.select(100);
} catch (NullPointerException err) {
LOG.warn("npe", err);
continue;
} catch (CancelledKeyException err) {
LOG.warn("cancelled", err);
continue;
} catch (IOException iox) {
throw new RuntimeException(iox);
}
Collection keys = selector.selectedKeys();
//if(LOG.isDebugEnabled())
// LOG.debug("Selected (" + keys.size() + ") keys.");
for(Iterator it = keys.iterator(); it.hasNext(); ) {
SelectionKey sk = (SelectionKey)it.next();
NIOHandler handler = (NIOHandler)sk.attachment();
try {
if (sk.isAcceptable())
processAccept(sk, (AcceptHandler)handler);
else if(sk.isConnectable())
processConnect(sk, (ConnectHandler)handler);
else {
if (sk.isReadable())
processRead(sk, (ReadHandler)handler);
if (sk.isWritable())
processWrite(sk, (WriteHandler)handler);
}
} catch (CancelledKeyException err) {
LOG.warn("Ignoring cancelled key", err);
} catch(IOException iox) {
LOG.warn("IOX processing", iox);
cancel(sk, handler);
handler.handleIOException(iox);
}
}
keys.clear();
}
}
/**
* The run loop
*/
public void run() {
while(true) {
try {
if(selector == null)
selector = Selector.open();
process();
} catch(Throwable err) {
selector = null;
LOG.error("Error in Selector!", err);
ErrorService.error(err);
}
}
}
/** Encapsulates a pending op. */
private static class PendingOp {
private final SelectableChannel channel;
private final NIOHandler handler;
private final int op;
PendingOp(SelectableChannel channel, NIOHandler handler, int op) {
this.channel = channel;
this.handler = handler;
this.op = op;
}
}
}