/* $Id$ */
package ibis.ipl.impl.nio;
import ibis.ipl.impl.ReceivePortIdentifier;
import java.io.IOException;
import java.nio.channels.GatheringByteChannel;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
final class NonBlockingChannelNioAccumulator extends NioAccumulator {
private static Logger logger = LoggerFactory.getLogger(
NonBlockingChannelNioAccumulator.class);
private final Selector selector;
public NonBlockingChannelNioAccumulator(NioSendPort port)
throws IOException {
super(port);
selector = Selector.open();
}
NioAccumulatorConnection newConnection(GatheringByteChannel channel,
ReceivePortIdentifier peer) throws IOException {
NioAccumulatorConnection result;
SelectableChannel sChannel = (SelectableChannel) channel;
sChannel.configureBlocking(false);
result = new NioAccumulatorConnection(port, channel, peer);
result.key = sChannel.register(selector, 0);
result.key.attach(result);
return result;
}
/**
* Sends out a buffer to multiple channels. First adds buffer to pending
* buffers list, then sends out as much data as possible
*/
boolean doSend(SendBuffer buffer) throws IOException {
if (logger.isDebugEnabled()) {
logger.debug("doSend()");
}
if (nrOfConnections == 0) {
logger.error("not connected");
return true;
} else if (nrOfConnections == 1) {
if (logger.isDebugEnabled()) {
logger.debug("sending to 1 connection");
}
NioAccumulatorConnection connection = connections[0];
try {
if (!connection.addToSendList(buffer)) {
if (logger.isDebugEnabled()) {
logger.debug(
"add failed, making room and trying again");
}
doFlush();
connection.addToSendList(buffer);
}
connection.send();
} catch (IOException e) {
port.lostConnection(connection.target, e);
nrOfConnections--;
}
} else {
if (logger.isDebugEnabled()) {
logger.debug("sending to " + nrOfConnections + " connections");
}
SendBuffer[] copies = SendBuffer.replicate(buffer, nrOfConnections);
for (int i = 0; i < nrOfConnections; i++) {
NioAccumulatorConnection connection = connections[i];
try {
if (!connection.addToSendList(copies[i])) {
doFlush(connection);
connection.addToSendList(copies[i]);
}
connection.send();
} catch (IOException e) {
// inform the SendPort
port.lostConnection(connection.target, e);
// remove connection
nrOfConnections--;
connections[i] = connections[nrOfConnections];
connections[nrOfConnections] = null;
SendBuffer.recycle(copies[nrOfConnections]);
i--;
}
}
}
return false;
}
void doFlush() throws IOException {
doFlush(null);
}
void doFlush(NioAccumulatorConnection connection) throws IOException {
int nrOfSendingConnections = 0;
NioAccumulatorConnection selected;
boolean done = false;
if (logger.isDebugEnabled()) {
if (connection == null) {
logger.debug("doing a complete flush");
} else {
logger.debug("doing a flush of a single connection");
}
}
if (logger.isDebugEnabled() && connection != null) {
boolean found = false;
for (int i = 0; i < nrOfConnections; i++) {
if (connections[i] == connection) {
found = true;
}
}
if (!found) {
throw new IOException("tried to flush non existing connection");
}
}
// first try to send out data one more time, and remember
// which connections still have data left
for (int i = 0; i < nrOfConnections; i++) {
NioAccumulatorConnection conn = connections[i];
try {
if (!conn.send()) {
conn.key.interestOps(SelectionKey.OP_WRITE);
nrOfSendingConnections++;
} else {
if (conn == connection) {
done = true;
}
conn.key.interestOps(0);
}
} catch (IOException e) {
port.lostConnection(conn.target, e);
nrOfConnections--;
connections[i] = connections[nrOfConnections];
connections[nrOfConnections] = null;
i--;
}
}
if (done || (nrOfSendingConnections == 0)) {
if (logger.isDebugEnabled()) {
logger.debug("flush done");
}
return;
}
if (logger.isDebugEnabled()) {
logger.debug("did one send for each connection" + ", "
+ nrOfSendingConnections + " connections with data"
+ " left");
}
// continually do a select and send data, until all data has been send
while (!done) {
selector.selectedKeys().clear();
try {
selector.select();
} catch (IOException e) {
// IGNORE
}
if (logger.isDebugEnabled()) {
logger.debug("selected " + selector.selectedKeys().size()
+ " channels");
}
for (SelectionKey key : selector.selectedKeys()) {
selected = (NioAccumulatorConnection) key.attachment();
try {
if (selected.send()) {
if (key.interestOps() == 0) {
throw new IOException("selected non-active channel");
}
key.interestOps(0);
nrOfSendingConnections--;
if (selected == connection
|| nrOfSendingConnections == 0) {
done = true;
if (logger.isDebugEnabled()) {
logger.debug("done flushing a connection, "
+ nrOfSendingConnections + " left");
}
}
}
} catch (IOException e) {
nrOfSendingConnections--;
for (int i = 0; i < nrOfConnections; i++) {
if (connection == connections[i]) {
port.lostConnection(connections[i].target, e);
nrOfConnections--;
connections[i] = connections[nrOfConnections];
connections[nrOfConnections] = null;
break;
}
}
}
}
}
if (logger.isDebugEnabled() && (connection == null)) {
for (int i = 0; i < nrOfConnections; i++) {
NioAccumulatorConnection conn = connections[i];
if (!conn.empty()) {
throw new IOException("data left to send after doing flush");
}
}
}
if (logger.isDebugEnabled()) {
logger.debug("flush done");
}
}
}