package com.trilead.ssh2.channel; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; /** * FIFO buffer for a reader thread and a writer thread to collaborate. * * Unlike a ring buffer, which uses a fixed memory regardless of the number of bytes currently in the buffer, * this implementation uses a single linked list to reduce the memory footprint when the reader * closely follows the writer, regardless of the capacity limit set in the constructor. * * In trilead, the writer puts the data we receive from the network, and the user code acts as a reader. * A user code normally drains the buffer more quickly than what the network delivers, so this implementation * saves memory while simultaneously allowing us to advertise a bigger window size for a large latency network. * * @author Kohsuke Kawaguchi */ class FifoBuffer { /** * Unit of buffer, singly linked and lazy created as needed. */ static final class Page { final byte[] buf; Page next; Page(int sz) { this.buf = new byte[sz]; } } /** * Points to a specific byte in a {@link Page}. */ class Pointer { Page p; /** * [0,p.buf.size) */ int off; Pointer(Page p, int off) { this.p = p; this.off = off; } /** * Figure out the number of bytes that can be read/written in one array copy. */ private int chunk() { int sz = pageSize-off; assert sz>=0; if (sz>0) return sz; Page q = p.next; if (q==null) q = p.next = newPage(); p = q; off = 0; return pageSize; } public void write(byte[] buf, int start, int len) { while (len>0) { int chunk = Math.min(len,chunk()); System.arraycopy(buf,start,p.buf,off,chunk); off+=chunk; len-=chunk; start+=chunk; } } public void read(byte[] buf, int start, int len) { while (len>0) { int chunk = Math.min(len,chunk()); System.arraycopy(p.buf,off,buf,start,chunk); off+=chunk; len-=chunk; start+=chunk; } } } private final Object lock; /** * Number of bytes currently in this ring buffer */ private int sz; /** * Cap to the # of bytes that we can hold. */ private int limit; private final int pageSize; /** * The position at which the next read/write will happen. */ private Pointer r,w; /** * Set to true when the writer closes the write end. */ private boolean closed; FifoBuffer(int pageSize, int limit) { this(null,pageSize,limit); } FifoBuffer(Object lock, int pageSize, int limit) { this.lock = lock==null ? this : lock; this.limit = limit; this.pageSize = pageSize; Page p = newPage(); r = new Pointer(p,0); w = new Pointer(p,0); } public void setLimit(int newLimit) { synchronized (lock) { limit = newLimit; } } private Page newPage() { return new Page(pageSize); } /** * Number of bytes readable */ int readable() { synchronized (lock) { return sz; } } /** * Number of bytes writable */ int writable() { return Math.max(0,limit-readable()); } public void write(byte[] buf, int start, int len) throws InterruptedException { while (len>0) { int chunk; synchronized (lock) { while ((chunk = Math.min(len,writable()))==0) lock.wait(); w.write(buf, start, chunk); start += chunk; len -= chunk; sz += chunk; lock.notifyAll(); } } } public void close() { synchronized (lock) { if (!closed) { closed = true; releaseRing(); lock.notifyAll(); } } } /** * If the ring is no longer needed, release the buffer. */ private void releaseRing() { if (closed && readable()==0) r = w = null; } /** * * @see InputStream#read(byte[],int,int) */ public int read(byte[] buf, int start, int len) throws InterruptedException { if (len==0) return 0; int read = 0; // total # of bytes read while (true) { int chunk; synchronized (lock) { while (true) { chunk = Math.min(len,readable()); if (chunk>0) break; // there's nothing we can immediately read if (read>0) return read; // we've already read some if (closed) { releaseRing(); return -1; // no more data } lock.wait(); // wait until the writer gives us something } r.read(buf,start,chunk); start += chunk; len -= chunk; read += chunk; sz -= chunk; lock.notifyAll(); } } } /** * Write whatever readable to the specified OutputStream, then return. */ public int writeTo(OutputStream out) throws IOException { try { int total = 0; while (readable()>0) { byte[] buf = new byte[1024]; // most often this method gets called before we have any data, so this is a win int read = read(buf, 0, buf.length); out.write(buf,0,read); total += read; } return total; } catch (InterruptedException e) { throw new AssertionError(e); // we carefully read only what we can read without blocking } } }