/* $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 BlockingChannelNioReceivePort extends NioReceivePort {
static final int INITIAL_DISSIPATOR_SIZE = 8;
private static Logger logger
= LoggerFactory.getLogger(BlockingChannelNioReceivePort.class);
private boolean closing = false;
private BlockingChannelNioDissipator[] connections
= new BlockingChannelNioDissipator[INITIAL_DISSIPATOR_SIZE];
private int nrOfConnections = 0;
BlockingChannelNioReceivePort(Ibis ibis, PortType type, String name,
MessageUpcall upcall, ReceivePortConnectUpcall connUpcall,
Properties properties) throws IOException {
super(ibis, type, name, upcall, connUpcall, properties);
}
synchronized void newConnection(SendPortIdentifier spi, Channel channel)
throws IOException {
if (!(channel instanceof ReadableByteChannel)) {
throw new IOException("wrong channel type on creating connection");
}
BlockingChannelNioDissipator dissipator
= new BlockingChannelNioDissipator((ReadableByteChannel) channel);
addConnection(spi, dissipator);
if (nrOfConnections == connections.length) {
BlockingChannelNioDissipator[] newConnections;
newConnections = new BlockingChannelNioDissipator[connections.length * 2];
for (int i = 0; i < connections.length; i++) {
newConnections[i] = connections[i];
}
connections = newConnections;
}
connections[nrOfConnections] = dissipator;
nrOfConnections++;
if (nrOfConnections > 1) {
logger.warn("" + nrOfConnections + " connections to a "
+ "blocking receiveport, added connection from " + spi
+ " to " + ident);
}
}
synchronized void errorOnRead(NioDissipator dissipator, Exception cause) {
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;
}
}
}
NioDissipator getReadyDissipator(long deadline) throws IOException {
Selector selector;
long time;
boolean deadlinePassed = false;
BlockingChannelNioDissipator dissipator = null;
SelectionKey[] keys = new SelectionKey[0];
synchronized (this) {
if (nrOfConnections == 0 && closing) {
throw new ConnectionClosedException();
}
for (int i = 0; i < nrOfConnections; i++) {
BlockingChannelNioDissipator conn = connections[i];
try {
if (conn.messageWaiting()) {
return conn;
}
} catch (IOException e) {
errorOnRead(conn, e);
i--;
}
}
if (nrOfConnections == 1
&& type.hasCapability(PortType.CONNECTION_ONE_TO_ONE)
&& deadline == 0) {
dissipator = connections[0];
}
}
// since we have only one connection, and no more are allowed, and
// we can wait for ever for data we just do a blocking
// receive here on the one channel
try {
while (dissipator != null && !dissipator.messageWaiting()) {
if (dissipator.available() == 0) {
try {
dissipator.receive();
} catch (IOException e) {
errorOnRead(dissipator, e);
dissipator = null;
break;
}
}
}
} catch (IOException e) {
errorOnRead(dissipator, e);
dissipator = null;
}
if (dissipator != null) {
// message waiting now
return dissipator;
}
while (!deadlinePassed) {
synchronized (this) {
if (nrOfConnections == 0) {
if (closing) {
throw new ConnectionClosedException();
}
if (deadline == -1) {
deadlinePassed = true;
continue;
} else if (deadline == 0) {
try {
wait();
} catch (InterruptedException e) {
// IGNORE
}
continue;
} else {
time = System.currentTimeMillis();
if (time >= deadline) {
deadlinePassed = true;
} else {
try {
wait();
} catch (InterruptedException e) {
// IGNORE
}
continue;
}
}
}
selector = Selector.open();
for (int i = 0; i < nrOfConnections; i++) {
SelectableChannel sh;
sh = (SelectableChannel) connections[i].channel;
sh.configureBlocking(false);
sh.register(selector, SelectionKey.OP_READ,
connections[i]);
}
}
try {
if (deadline == -1) {
selector.selectNow();
deadlinePassed = true;
} else if (deadline == 0) {
selector.select();
} else {
time = System.currentTimeMillis();
if (time >= deadline) {
deadlinePassed = true;
} else {
selector.select(deadline - time);
}
}
} catch (IOException e) {
// FIXME: is this a good idea?
// IGNORE
logger.debug("Got IOException", e);
}
keys = selector.selectedKeys().toArray(keys);
selector.close();
synchronized (this) {
for (int i = 0; i < nrOfConnections; i++) {
SelectableChannel sh;
sh = (SelectableChannel)connections[i].channel;
sh.configureBlocking(true);
}
}
for (int i = 0; i < keys.length; i++) {
dissipator = (BlockingChannelNioDissipator) keys[i]
.attachment();
SelectableChannel sh;
sh = (SelectableChannel) dissipator.channel;
sh.configureBlocking(true);
try {
dissipator.readFromChannel();
} catch (IOException e) {
errorOnRead(dissipator, e);
}
}
synchronized (this) {
if (nrOfConnections == 0 && closing) {
throw new ConnectionClosedException();
}
for (int i = 0; i < nrOfConnections; i++) {
try {
if (connections[i].messageWaiting()) {
return connections[i];
}
} catch (IOException e) {
errorOnRead(connections[i], e);
i--;
}
}
}
}
throw new ReceiveTimedOutException("timeout while selecting"
+ " dissipator");
}
synchronized void closing() {
closing = true;
}
}