/* $Id$ */ package ibis.ipl.impl.nio; import ibis.io.Conversion; import ibis.io.DataInputStream; import ibis.ipl.impl.ReceivePortIdentifier; import java.io.EOFException; import java.io.IOException; import java.nio.Buffer; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.CharBuffer; import java.nio.DoubleBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.nio.LongBuffer; import java.nio.ShortBuffer; import java.nio.channels.ReadableByteChannel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Reads data into a single bytebuffer, and creates views of it to drain it. * Depends upon a subclass to do the actual reading from somewhere */ public abstract class NioDissipator extends DataInputStream implements Config, Protocol { public static final int SIZEOF_BYTE = 1; public static final int SIZEOF_CHAR = 2; public static final int SIZEOF_SHORT = 2; public static final int SIZEOF_INT = 4; public static final int SIZEOF_LONG = 8; public static final int SIZEOF_FLOAT = 4; public static final int SIZEOF_DOUBLE = 8; // maximum number of bytes we want to put in the receivebuffer. The extra // space we use if a view happens to be split up between the send and // the beginning of the buffer. We can then copy the wrapped part to the // "hidden space" at the end. protected static final int BUFFER_LIMIT = BYTE_BUFFER_SIZE - PRIMITIVE_BUFFER_SIZE; private static final int LONGS = 1; private static final int DOUBLES = 2; private static final int INTS = 3; private static final int FLOATS = 4; private static final int SHORTS = 5; private static final int CHARS = 6; private static final int BYTES = 7; protected static final int SIZEOF_HEADER = 16; private static Logger logger = LoggerFactory.getLogger(NioDissipator.class); /** * Circular buffer used for holding data. It contains "currently in use" (by * the user) data, "not yet used" data (received but not given to user yet) * and "empty space" */ protected ByteBuffer buffer; /** * Buffer with the same backing-store as the buffer, used for copying data * from one place to another in the buffer */ private ByteBuffer copyFromBuffer; private ByteBuffer copyToBuffer; // variables used to keep track of the state of the "buffer" // They abide by the following equasion: // usedPosition <= usedLimit <= buffer.position() ( % BUFFER_LIMIT !) protected int usedPosition = 0; // first byte of in-use data protected int usedLimit = 0; // fist byte of not-used data (or empty // space) private ShortBuffer header; private LongBuffer longs; private DoubleBuffer doubles; private FloatBuffer floats; private IntBuffer ints; private ShortBuffer shorts; private CharBuffer chars; private ByteBuffer bytes; private ByteOrder order; private long count = 0; NioReceivePort.ConnectionInfo info; ReadableByteChannel channel; protected NioDissipator(ReadableByteChannel channel) throws IOException { this.channel = channel; order = ByteOrder.BIG_ENDIAN; buffer = ByteBuffer.allocateDirect(BYTE_BUFFER_SIZE); copyFromBuffer = buffer.duplicate(); copyToBuffer = buffer.duplicate(); buffer.limit(BUFFER_LIMIT); initViews(order); // make the views appear empty header.limit(0); longs.limit(0); doubles.limit(0); floats.limit(0); ints.limit(0); shorts.limit(0); chars.limit(0); bytes.limit(0); } public int bufferSize() { return -1; } /** * (re) Initialize buffers in the right byte order. */ protected void initViews(ByteOrder order) { int position; int limit; if (logger.isDebugEnabled()) { logger.debug("initializing views in " + order + " byte order"); } // remember position and limit; position = buffer.position(); limit = buffer.limit(); buffer.order(order); // clear so views will be set correctly buffer.clear(); header = buffer.asShortBuffer(); longs = buffer.asLongBuffer(); doubles = buffer.asDoubleBuffer(); floats = buffer.asFloatBuffer(); ints = buffer.asIntBuffer(); shorts = buffer.asShortBuffer(); chars = buffer.asCharBuffer(); bytes = buffer.duplicate(); buffer.position(position); buffer.limit(limit); } /** * Sets a view correctly. * * All received data is garanteed to be alligned since we always send * buffers with ((size % MAX_DATA_SIZE) == 0) */ private int setView(Buffer view, int start, int bytes, int dataSize) { int result = (start + bytes) % BUFFER_LIMIT; if (result < start) { // it wrapped, copy data from the start to the end of the buffer copyFromBuffer.position(0); copyFromBuffer.limit(result); copyToBuffer.position(BUFFER_LIMIT); copyToBuffer.limit(start + bytes); copyToBuffer.put(copyFromBuffer); } view.limit((start + bytes) / dataSize); view.position(start / dataSize); if (logger.isDebugEnabled()) { logger.debug("setView: set view: position(" + view.position() + ") limit(" + view.limit() + "), in bytes: position(" + (view.position() * dataSize) + ") limit(" + (view.limit() * dataSize) + ")"); } return result; } /** * Returns the number of bytes available in the receivebuffer which haven't * been "claimed" for user data yet */ int unUsedLength() { if (buffer.position() >= usedLimit) { return buffer.position() - usedLimit; } return buffer.position() + (BUFFER_LIMIT - usedLimit); } /** * Returns the nuber of bytes "claimed" for user data */ int usedLength() { if (usedLimit >= usedPosition) { return usedPosition - usedLimit; } return usedLimit + (BUFFER_LIMIT - usedPosition); } void receive() throws IOException { ByteOrder receivedOrder; int next; int totalSize; int paddingLength; short[] headerArray = new short[SIZEOF_HEADER / SIZEOF_SHORT]; if (logger.isDebugEnabled()) { logger.debug("receiving buffer"); if (remaining() > 0) { logger.error("receiving with data still left in the buffer" + ", content: " + "l[" + longs.remaining() + "] d[" + doubles.remaining() + "] i[" + ints.remaining() + "] f[" + floats.remaining() + "] s[" + shorts.remaining() + "] c[" + chars.remaining() + "] b[" + bytes.remaining() + "]"); throw new IOException("tried receive() while there was data" + " left in the buffer"); } } // release old used data usedPosition = usedLimit; // remember we can use the newly freed space to put data in if (buffer.limit() != BUFFER_LIMIT) { if (buffer.position() < usedPosition) { buffer.limit(usedPosition - 1); } else if (buffer.position() >= usedLimit) { buffer.limit(BUFFER_LIMIT); } } if (logger.isDebugEnabled()) { logger.debug("usedPosition = " + usedPosition + " buffer.position(" + buffer.position() + ") buffer.limit(" + buffer.limit() + ")"); } if (unUsedLength() < SIZEOF_HEADER) { fillBuffer(SIZEOF_HEADER); } bytes.clear(); // extract padding length paddingLength = bytes.get(usedPosition + 1); // get byte order out of first byte in header if (bytes.get(usedPosition) == ((byte) 1)) { receivedOrder = ByteOrder.BIG_ENDIAN; } else { receivedOrder = ByteOrder.LITTLE_ENDIAN; } if (order != receivedOrder) { // our buffers are in the wrong order, re-initialize order = receivedOrder; initViews(order); } next = setView(header, usedPosition, SIZEOF_HEADER, SIZEOF_SHORT); // extract header header.get(headerArray); totalSize = SIZEOF_HEADER + headerArray[LONGS] + headerArray[DOUBLES] + headerArray[INTS] + headerArray[FLOATS] + headerArray[SHORTS] + headerArray[CHARS] + headerArray[BYTES] + paddingLength; if (logger.isDebugEnabled()) { logger.debug("total size of buffer we're receiving is: " + totalSize + " padding: " + paddingLength); } if (unUsedLength() < totalSize) { fillBuffer(totalSize); } // claim space usedLimit = (usedPosition + totalSize) % BUFFER_LIMIT; next = setView(longs, next, headerArray[LONGS], SIZEOF_LONG); next = setView(doubles, next, headerArray[DOUBLES], SIZEOF_DOUBLE); next = setView(ints, next, headerArray[INTS], SIZEOF_INT); next = setView(floats, next, headerArray[FLOATS], SIZEOF_FLOAT); next = setView(shorts, next, headerArray[SHORTS], SIZEOF_SHORT); next = setView(chars, next, headerArray[CHARS], SIZEOF_CHAR); next = setView(bytes, next, headerArray[BYTES], SIZEOF_BYTE); if (logger.isDebugEnabled()) { logger.debug("received: l[" + longs.remaining() + "] d[" + doubles.remaining() + "] i[" + ints.remaining() + "] f[" + floats.remaining() + "] s[" + shorts.remaining() + "] c[" + chars.remaining() + "] b[" + bytes.remaining() + "]"); } } /** * Returns if there is a message waiting. Will not try to receive data * unless there is a command, but the operands are not received yet. Also * inits serialization stream if needed * * @throws IOException * if an error occured, or the peer closed the connection. */ boolean messageWaiting() throws IOException { byte command; // Moved here to prevent deadlocks and timeouts when using sun // serialization -- Jason if (info.in == null) { info.newStream(); } while (true) { if (info.in.available() == 0) { // no data available at all return false; } command = info.in.readByte(); // create new input stream if needed switch (command) { case NEW_RECEIVER: info.newStream(); break; case NEW_MESSAGE: return true; case CLOSE_ALL_CONNECTIONS: info.close(null); throw new IOException("connection closed by peer"); case CLOSE_ONE_CONNECTION: byte[] length = new byte[Conversion.INT_SIZE]; info.in.readArray(length); byte[] bytes = new byte[Conversion.defaultConversion .byte2int(length, 0)]; info.in.readArray(bytes); ReceivePortIdentifier identifier = new ReceivePortIdentifier(bytes); if (identifier.equals(info.port.ident)) { info.close(null); throw new IOException("connection closed by peer"); } break; default: throw new IOException("unknown opcode in command"); } } } /** * Reads data from the channel into the buffer ONCE. Wraps the buffer if * needed. */ void readFromChannel() throws IOException { int count; if (logger.isDebugEnabled()) { logger.debug("reading into buffer, position(" + buffer.position() + ") limit(" + buffer.limit() + ")"); } count = channel.read(buffer); if (count == -1) { throw new IOException("END-OF-STREAM encountered"); } this.count += count; if (logger.isDebugEnabled()) { logger.debug("read " + count + " bytes, total" + " bytes read now " + this.count); } if ((!buffer.hasRemaining()) && (buffer.limit() == BUFFER_LIMIT) && (usedPosition > 0)) { // wrap around buffer.position(0); buffer.limit(usedPosition - 1); if (logger.isDebugEnabled()) { logger.debug("buffer wrapped, position(" + buffer.position() + ") limit(" + buffer.limit() + ")"); } } } int remaining() { return ((longs.remaining() * SIZEOF_LONG) + (doubles.remaining() * SIZEOF_DOUBLE) + (ints.remaining() * SIZEOF_INT) + (floats.remaining() * SIZEOF_INT) + (shorts.remaining() * SIZEOF_SHORT) + (chars.remaining() * SIZEOF_CHAR) + (bytes.remaining() * SIZEOF_BYTE)); } /** * returns the number of bytes garanteed to be readable without blocking. * */ public int available() { int result = remaining(); if (unUsedLength() > 0) { // mapping of unUnsedLength to really readable bytes is // unpredictable // so we assume there's only 1 byte in it result += 1; } if (logger.isDebugEnabled()) { logger.debug("available: " + result); } return result; } /** * returns if there is any data that hasn't been read. */ boolean dataLeft() { try { return (info.in.available() != 0); } catch (IOException e) { return false; } } public long bytesRead() { return count; } public void resetBytesRead() { count = 0; } public boolean readBoolean() throws IOException { return (readByte() == ((byte) 1)); } public byte readByte() throws IOException { byte result; try { result = bytes.get(); } catch (BufferUnderflowException e) { receive(); result = bytes.get(); } if (logger.isDebugEnabled()) { logger.debug("received byte: " + result); } return result; } public int read() throws IOException { try { return readByte() & 0377; } catch (EOFException e) { return -1; } } public char readChar() throws IOException { try { return chars.get(); } catch (BufferUnderflowException e) { receive(); return chars.get(); } } public short readShort() throws IOException { try { return shorts.get(); } catch (BufferUnderflowException e) { receive(); return shorts.get(); } } public int readInt() throws IOException { try { return ints.get(); } catch (BufferUnderflowException e) { receive(); return ints.get(); } } public long readLong() throws IOException { try { return longs.get(); } catch (BufferUnderflowException e) { receive(); return longs.get(); } } public float readFloat() throws IOException { try { return floats.get(); } catch (BufferUnderflowException e) { receive(); return floats.get(); } } public double readDouble() throws IOException { try { return doubles.get(); } catch (BufferUnderflowException e) { receive(); return doubles.get(); } } public void readArray(boolean ref[], int off, int len) throws IOException { for (int i = off; i < (off + len); i++) { ref[i] = ((readByte() == (byte) 1) ? true : false); } } public void readArray(byte ref[], int off, int len) throws IOException { try { bytes.get(ref, off, len); } catch (BufferUnderflowException e) { // do this the hard way int left = len; int offset = off; int size; while (left > 0) { // copy as much as possible to the buffer size = Math.min(left, bytes.remaining()); bytes.get(ref, offset, size); offset += size; left -= size; // if still needed, fetch some more bytes from the // channel if (left > 0) { receive(); } } } if (logger.isDebugEnabled()) { String message = "received byte[], Contents: "; for (int i = off; i < (off + len); i++) { message = message + ref[i] + " "; } logger.debug(message); } } public int read(byte[] ref) throws IOException { return read(ref, 0, ref.length); } public int read(byte ref[], int off, int len) throws IOException { try { bytes.get(ref, off, len); } catch (BufferUnderflowException e) { // do this the hard way int left = len; int offset = off; int size; try { while (left > 0) { // copy as much as possible to the buffer size = Math.min(left, bytes.remaining()); bytes.get(ref, offset, size); offset += size; left -= size; // if still needed, fetch some more bytes from the // channel if (left > 0) { receive(); } } } catch (EOFException e2) { len = offset - off; if (len == 0) { return -1; } } } if (logger.isDebugEnabled()) { String message = "received byte[], Contents: "; for (int i = off; i < (off + len); i++) { message = message + ref[i] + " "; } logger.debug(message); } return len; } public void readArray(char ref[], int off, int len) throws IOException { try { chars.get(ref, off, len); } catch (BufferUnderflowException e) { // do this the hard way int left = len; int size; while (left > 0) { // copy as much as possible to the buffer size = Math.min(left, chars.remaining()); chars.get(ref, off, size); off += size; left -= size; // if still needed, fetch some more bytes from the // channel if (left > 0) { receive(); } } } } public void readArray(short ref[], int off, int len) throws IOException { try { shorts.get(ref, off, len); } catch (BufferUnderflowException e) { // do this the hard way int left = len; int size; while (left > 0) { // copy as much as possible to the buffer size = Math.min(left, shorts.remaining()); shorts.get(ref, off, size); off += size; left -= size; // if still needed, fetch some more bytes from the // channel if (left > 0) { receive(); } } } } public void readArray(int ref[], int off, int len) throws IOException { try { ints.get(ref, off, len); } catch (BufferUnderflowException e) { // do this the hard way int left = len; int size; while (left > 0) { // copy as much as possible to the buffer size = Math.min(left, ints.remaining()); ints.get(ref, off, size); off += size; left -= size; // if still needed, fetch some more bytes from the // channel if (left > 0) { receive(); } } } } public void readArray(long ref[], int off, int len) throws IOException { try { longs.get(ref, off, len); } catch (BufferUnderflowException e) { // do this the hard way int left = len; int size; while (left > 0) { // copy as much as possible to the buffer size = Math.min(left, longs.remaining()); longs.get(ref, off, size); off += size; left -= size; // if still needed, fetch some more bytes from the // channel if (left > 0) { receive(); } } } } public void readArray(float ref[], int off, int len) throws IOException { try { floats.get(ref, off, len); } catch (BufferUnderflowException e) { // do this the hard way int left = len; int size; while (left > 0) { // copy as much as possible to the buffer size = Math.min(left, floats.remaining()); floats.get(ref, off, size); off += size; left -= size; // if still needed, fetch some more bytes from the // channel if (left > 0) { receive(); } } } } public void readArray(double ref[], int off, int len) throws IOException { try { doubles.get(ref, off, len); } catch (BufferUnderflowException e) { // do this the hard way int left = len; int size; while (left > 0) { // copy as much as possible to the buffer size = Math.min(left, doubles.remaining()); doubles.get(ref, off, size); off += size; left -= size; // if still needed, fetch some more bytes from the // channel if (left > 0) { receive(); } } } } public void readByteBuffer(ByteBuffer b) throws IOException { if (b.hasArray()) { readArray(b.array(), b.arrayOffset(), b.limit() - b.position()); b.position(b.limit()); } else { byte[] buf = new byte[b.limit() - b.position()]; readArray(buf); b.put(buf); } } /** * fills the buffer upto at least "minimum" bytes. * * @throws IOException * if an error occured on reading from the channel */ protected abstract void fillBuffer(int minimum) throws IOException; /** * Closes this dissipator. */ public void close() throws IOException { channel.close(); } }