package lsr.paxos.network; import static lsr.common.ProcessDescriptor.processDescriptor; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; import java.util.ArrayDeque; import java.util.Iterator; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import lsr.common.PID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class NioOutputConnection extends NioConnection { private static final int MAX_PENDING_DATA = 1024; private static final int DRAIN_LIMIT = 64; private static final int BUFFER_SIZE = 8192; private BlockingQueue<byte[]> pendingData = new ArrayBlockingQueue<byte[]>( MAX_PENDING_DATA); private ArrayDeque<byte[]> drained = new ArrayDeque<byte[]>(DRAIN_LIMIT); ByteBuffer outputBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE); byte[] currentData; int currentDataWritten = 0; private volatile boolean connected = false; public NioOutputConnection(NioNetwork network, PID replica, SocketChannel channel) throws IOException { super(network, replica, channel); if (channel != null) { channel.register(selector, SelectionKey.OP_WRITE); connected = true; outputBuffer.flip(); logger.debug("output connection established to: {}", replicaId); } // if (network.localId == 0) // pendingData = new ArrayBlockingQueue<byte[]>(100 * 1024); } public boolean send(byte[] data) { // if (!connected) // return false; try { boolean success = pendingData.offer(data); if (!success) { // long time = System.currentTimeMillis(); pendingData.put(data); // Network.writeWaitTime.add(data[4], System.currentTimeMillis() // - time); } } catch (InterruptedException e) { e.printStackTrace(); } return true; } protected void tryConnect() throws IOException { logger.debug("trying to connect to: {}", replicaId); channel = SocketChannel.open(); network.configureChannel(channel); channel.connect(new InetSocketAddress(replica.getHostname(), replica.getReplicaPort())); channel.register(selector, SelectionKey.OP_CONNECT); } protected void introduce() { logger.trace("introducing myself to: {}", replicaId); // ByteBuffer myIdByteBuffer = ByteBuffer.allocate(4); // myIdByteBuffer.putInt(network.localId); // myIdByteBuffer.flip(); outputBuffer.clear(); outputBuffer.putInt(4); outputBuffer.putInt(Network.localId); outputBuffer.flip(); // selector.wakeup(); // pendingData.offer(myIdByteBuffer); } @Override public void run() { String name = "NioOutputConnection_" + Network.localId + "->" + replicaId; setName(name); logger.debug("starting {}", name); while (true) { try { if (!connected) { disposeConnection(); if (isActiveConnection()) tryConnect(); else return; } else { if (outputBuffer.remaining() == 0) fillBuffer(); } selector.select(); Iterator<SelectionKey> selectedKeys = selector.selectedKeys() .iterator(); while (selectedKeys.hasNext()) { SelectionKey key = (SelectionKey) selectedKeys.next(); selectedKeys.remove(); if (!key.isValid()) continue; if (key.isConnectable()) finishConnect(key); if (key.isWritable()) write(key); // if (key.isReadable()) // disposeConnection(); } } catch (Exception e) { logger.debug("output connection problem with: {}", replicaId); connected = false; } if (!connected && isActiveConnection()) { try { sleep(processDescriptor.tcpReconnectTimeout); } catch (InterruptedException e) { logger.trace("waking up early to try to connect"); } } } } private void fillBuffer() throws InterruptedException { logger.trace("filling buffer"); outputBuffer.clear(); while (true) { if (currentData != null) { logger.trace("copying data to buffer"); int bytesToCopy = Math.min(currentData.length - currentDataWritten, outputBuffer.remaining()); outputBuffer.put(currentData, currentDataWritten, bytesToCopy); currentDataWritten += bytesToCopy; if (currentDataWritten == currentData.length) { logger.trace("currentData written entirely"); currentData = null; } } if (outputBuffer.remaining() < 4 || (outputBuffer.position() != 0 && noPendingData())) { logger.trace("preparing buffer for sending"); outputBuffer.flip(); return; } if (currentData == null) { logger.trace("taking new data from the queue"); currentData = getNewData(); currentDataWritten = 0; outputBuffer.putInt(currentData.length); } } } private boolean noPendingData() { // return pendingData.isEmpty(); if (!drained.isEmpty()) return false; pendingData.drainTo(drained, DRAIN_LIMIT); return drained.isEmpty(); } private byte[] getNewData() throws InterruptedException { // return pendingData.take(); if (!drained.isEmpty()) return drained.removeFirst(); else { pendingData.drainTo(drained, DRAIN_LIMIT); if (!drained.isEmpty()) return drained.removeFirst(); else { return pendingData.take(); } } } private void write(SelectionKey key) throws IOException { int count = channel.write(outputBuffer); logger.trace("writing to: {} ( bytes)", replicaId, count); } private void finishConnect(SelectionKey key) throws IOException { channel.finishConnect(); connected = true; key.interestOps(SelectionKey.OP_WRITE); logger.debug("output connection established to: {} (finishConnect)", replicaId); NioInputConnection inputConnection = new NioInputConnection(network, replica, channel); network.connections[replicaId][0] = inputConnection; inputConnection.start(); introduce(); } public void notifyAboutInputConnected() { if (!connected) { logger.debug( "got notification about input established, trying to establish output: {}", replicaId); this.interrupt(); } } public void notifyAboutInputDisconnected() { connected = false; this.interrupt(); logger.debug("got notification about input disconnected, disconnecting myself: {}", replicaId); } protected void disposeConnection() { connected = false; currentData = null; for (SelectionKey key : selector.keys()) key.cancel(); pendingData.clear(); logger.debug("output connection closed with: {}", replicaId); network.removeConnection(Network.localId, replicaId); } private final static Logger logger = LoggerFactory.getLogger(Network.class); }