/* $Id$ */
package ibis.ipl.impl.nio;
import ibis.ipl.ConnectionClosedException;
import ibis.ipl.MessageUpcall;
import ibis.ipl.PortType;
import ibis.ipl.ReceivePortConnectUpcall;
import ibis.ipl.ReceiveTimedOutException;
import ibis.ipl.impl.Ibis;
import ibis.ipl.impl.SendPortIdentifier;
import java.io.IOException;
import java.nio.channels.Channel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
final class NonBlockingChannelNioReceivePort extends NioReceivePort {
static final int INITIAL_ARRAY_SIZE = 8;
private static Logger logger
= LoggerFactory.getLogger(NonBlockingChannelNioReceivePort.class);
private NonBlockingChannelNioDissipator[] connections
= new NonBlockingChannelNioDissipator[INITIAL_ARRAY_SIZE];
private int nrOfConnections = 0;
private NonBlockingChannelNioDissipator[] pendingConnections
= new NonBlockingChannelNioDissipator[INITIAL_ARRAY_SIZE];
private int nrOfPendingConnections = 0;
private boolean closing = false;
Selector selector;
NonBlockingChannelNioReceivePort(Ibis ibis, PortType type,
String name, MessageUpcall upcall, ReceivePortConnectUpcall connUpcall,
Properties props) throws IOException {
super(ibis, type, name, upcall, connUpcall, props);
selector = Selector.open();
}
synchronized void newConnection(SendPortIdentifier spi, Channel channel)
throws IOException {
if (logger.isDebugEnabled()) {
logger.debug("registering new connection");
}
if (!(channel instanceof ReadableByteChannel)) {
logger.error("wrong channel type");
throw new IOException("wrong channel type on creating connection");
}
NonBlockingChannelNioDissipator dissipator
= new NonBlockingChannelNioDissipator(
(ReadableByteChannel) channel);
addConnection(spi, dissipator);
if (nrOfConnections == connections.length) {
NonBlockingChannelNioDissipator[] newConnections;
newConnections = new NonBlockingChannelNioDissipator[connections.length * 2];
for (int i = 0; i < connections.length; i++) {
newConnections[i] = connections[i];
}
connections = newConnections;
}
connections[nrOfConnections] = dissipator;
nrOfConnections++;
if (nrOfPendingConnections == pendingConnections.length) {
NonBlockingChannelNioDissipator[] newPendingConnections;
newPendingConnections = new NonBlockingChannelNioDissipator[pendingConnections.length * 2];
for (int i = 0; i < pendingConnections.length; i++) {
newPendingConnections[i] = pendingConnections[i];
}
pendingConnections = newPendingConnections;
}
pendingConnections[nrOfPendingConnections] = dissipator;
nrOfPendingConnections++;
if (logger.isDebugEnabled()) {
logger.debug("waking up selector");
}
// wake up selector if needed
selector.wakeup();
if (logger.isDebugEnabled()) {
logger.debug("registerred new connection");
}
}
synchronized void errorOnRead(NioDissipator dissipator, Exception cause) {
logger.debug("lost connection", cause);
for (int i = 0; i < nrOfPendingConnections; i++) {
if (dissipator == pendingConnections[i]) {
nrOfPendingConnections--;
pendingConnections[i] = pendingConnections[nrOfPendingConnections];
pendingConnections[nrOfPendingConnections] = null;
logger.debug("lost connection removed from pending list");
break;
}
}
dissipator.info.close(cause);
for (int i = 0; i < nrOfConnections; i++) {
if (dissipator == connections[i]) {
nrOfConnections--;
connections[i] = connections[nrOfConnections];
connections[nrOfConnections] = null;
logger.debug("removed connection");
break;
}
}
if (nrOfConnections == 0) {
if (logger.isDebugEnabled()) {
logger.debug("no more connections, waking up selector");
}
selector.wakeup();
}
if (logger.isDebugEnabled()) {
logger.debug("removed connection");
}
}
synchronized void registerPendingConnections() throws IOException {
SelectableChannel sh;
if (logger.isDebugEnabled() && nrOfPendingConnections > 0) {
logger.debug("registerring " + nrOfPendingConnections
+ " connections");
}
for (int i = 0; i < nrOfPendingConnections; i++) {
sh = (SelectableChannel) pendingConnections[i].channel;
sh.register(selector, SelectionKey.OP_READ, pendingConnections[i]);
pendingConnections[i] = null;
}
nrOfPendingConnections = 0;
}
NioDissipator getReadyDissipator(long deadline) throws IOException {
boolean deadlinePassed = false;
boolean firstTry = true;
long time;
NonBlockingChannelNioDissipator dissipator = null;
if (logger.isDebugEnabled()) {
logger.debug("trying to find a dissipator"
+ " with a message waiting");
}
while (!deadlinePassed) {
registerPendingConnections();
synchronized (this) {
if (nrOfConnections == 0) {
if (closing) {
if (logger.isInfoEnabled()) {
logger.info("exiting because we have no "
+ "connections (as requested)");
}
throw new ConnectionClosedException();
}
if (deadline == -1) {
deadlinePassed = true;
continue;
} else if (deadline == 0) {
try {
if (logger.isDebugEnabled()) {
logger.debug("wait()ing for a connection");
}
wait();
} catch (InterruptedException e) {
// IGNORE
}
continue;
} else {
time = System.currentTimeMillis();
if (time >= deadline) {
deadlinePassed = true;
} else {
try {
if (logger.isDebugEnabled()) {
logger.debug("wait()ing for a connection");
}
wait();
} catch (InterruptedException e) {
// ignored
}
continue;
}
}
}
if (firstTry && nrOfConnections == 1) {
// optimisticly do a single receive, to avoid
// the select statement below if possible
try {
connections[0].readFromChannel();
} catch (IOException e) {
errorOnRead(connections[0], e);
}
firstTry = false;
}
for (int i = 0; i < nrOfConnections; i++) {
try {
if (connections[i].messageWaiting()) {
if (logger.isDebugEnabled()) {
logger.debug("returning connection " + i);
}
return connections[i];
}
} catch (IOException e) {
errorOnRead(connections[i], e);
i--;
}
}
} // end of synchronized block
if (deadline == -1) {
if (logger.isDebugEnabled()) {
logger.debug("doing a selectNow");
}
try {
selector.selectNow();
} catch (IOException e) {
// IGNORE
}
deadlinePassed = true;
} else if (deadline == 0) {
if (logger.isDebugEnabled()) {
logger.debug("doing a select() on "
+ selector.keys().size() + " connections");
}
try {
selector.select();
} catch (IOException e) {
logger.error("error on select: " + e);
// IGNORE
}
} else {
time = System.currentTimeMillis();
if (time >= deadline) {
deadlinePassed = true;
} else {
if (logger.isDebugEnabled()) {
logger.debug("doing a select(timeout)");
}
try {
selector.select(deadline - time);
} catch (IOException e) {
logger.error("error on select: " + e);
// IGNORE
}
}
}
if (logger.isDebugEnabled()) {
logger.debug("selected " + selector.selectedKeys().size()
+ " connections");
}
for (SelectionKey key : selector.selectedKeys()) {
dissipator = (NonBlockingChannelNioDissipator) key.attachment();
try {
dissipator.readFromChannel();
} catch (IOException e) {
errorOnRead(dissipator, e);
}
}
selector.selectedKeys().clear();
} // end of while(!deadlinePassed)
if (logger.isDebugEnabled()) {
logger.debug("deadline passed");
}
throw new ReceiveTimedOutException("timeout while waiting"
+ " for dissipator");
}
synchronized void closing() {
closing = true;
}
}