package water; import java.io.*; import java.lang.reflect.Array; import java.net.*; import java.nio.*; import java.nio.channels.*; import java.util.ArrayList; import java.util.Random; import water.network.SocketChannelUtils; import water.util.Log; import water.util.StringUtils; import water.util.TwoDimTable; /** A ByteBuffer backed mixed Input/Output streaming class, using Iced serialization. * * Reads/writes empty/fill the ByteBuffer as needed. When it is empty/full it * we go to the ByteChannel for more/less. Because DirectByteBuffers are * expensive to make, we keep a few pooled. * * When talking to a remote H2O node, switches between UDP and TCP transport * protocols depending on the message size. The TypeMap is not included, and * is assumed to exist on the remote H2O node. * * Supports direct NIO FileChannel read/write to disk, used during user-mode * swapping. The TypeMap is not included on write, and is assumed to be the * current map on read. * * Support read/write from byte[] - and this defeats the purpose of a * Streaming protocol, but is frequently handy for small structures. The * TypeMap is not included, and is assumed to be the current map on read. * * Supports read/write from a standard Stream, which by default assumes it is * NOT going in and out of the same Cloud, so the TypeMap IS included. The * serialized object can only be read back into the same minor version of H2O. * * @author <a href="mailto:cliffc@h2o.ai"></a> */ public final class AutoBuffer { // The direct ByteBuffer for schlorping data about. // Set to null to indicate the AutoBuffer is closed. ByteBuffer _bb; public String sourceName = "???"; public boolean isClosed() { return _bb == null ; } // The ByteChannel for moving data in or out. Could be a SocketChannel (for // a TCP connection) or a FileChannel (spill-to-disk) or a DatagramChannel // (for a UDP connection). Null on closed AutoBuffers. Null on initial // remote-writing AutoBuffers which are still deciding UDP vs TCP. Not-null // for open AutoBuffers doing file i/o or reading any TCP/UDP or having // written at least one buffer to TCP/UDP. private ByteChannel _chan; // A Stream for moving data in or out. Null unless this AutoBuffer is // stream-based, in which case _chan field is null. This path supports // persistance: reading and writing objects from different H2O cluster // instances (but exactly the same H2O revision). The only required // similarity is same-classes-same-fields; changes here will probably // silently crash. If the fields are named the same but the semantics // differ, then again the behavior is probably silent crash. private OutputStream _os; private InputStream _is; private short[] _typeMap; // Mapping from input stream map to current map, or null // If we need a SocketChannel, raise the priority so we get the I/O over // with. Do not want to have some TCP socket open, blocking the TCP channel // and then have the thread stalled out. If we raise the priority - be sure // to lower it again. Note this is for TCP channels ONLY, and only because // we are blocking another Node with I/O. private int _oldPrior = -1; // Where to send or receive data via TCP or UDP (choice made as we discover // how big the message is); used to lazily create a Channel. If NULL, then // _chan should be a pre-existing Channel, such as a FileChannel. final H2ONode _h2o; // TRUE for read-mode. FALSE for write-mode. Can be flipped for rapid turnaround. private boolean _read; // TRUE if this AutoBuffer has never advanced past the first "page" of data. // The UDP-flavor, port# and task fields are only valid until we read over // them when flipping the ByteBuffer to the next chunk of data. Used in // asserts all over the place. private boolean _firstPage; // Total size written out from 'new' to 'close'. Only updated when actually // reading or writing data, or after close(). For profiling only. int _size; //int _zeros, _arys; // More profiling: start->close msec, plus nano's spent in blocking I/O // calls. The difference between (close-start) and i/o msec is the time the // i/o thread spends doing other stuff (e.g. allocating Java objects or // (de)serializing). long _time_start_ms, _time_close_ms, _time_io_ns; // I/O persistence flavor: Value.ICE, NFS, HDFS, S3, TCP. Used to record I/O time. final byte _persist; // The assumed max UDP packetsize static final int MTU = 1500-8/*UDP packet header size*/; // Enable this to test random TCP fails on open or write static final Random RANDOM_TCP_DROP = null; //new Random(); static final java.nio.charset.Charset UTF_8 = java.nio.charset.Charset.forName("UTF-8"); /** Incoming UDP request. Make a read-mode AutoBuffer from the open Channel, * figure the originating H2ONode from the first few bytes read. */ AutoBuffer( DatagramChannel sock ) throws IOException { _chan = null; _bb = BBP_SML.make(); // Get a small / UDP-sized ByteBuffer _read = true; // Reading by default _firstPage = true; // Read a packet; can get H2ONode from 'sad'? Inet4Address addr = null; SocketAddress sad = sock.receive(_bb); if( sad instanceof InetSocketAddress ) { InetAddress address = ((InetSocketAddress) sad).getAddress(); if( address instanceof Inet4Address ) { addr = (Inet4Address) address; } } _size = _bb.position(); _bb.flip(); // Set limit=amount read, and position==0 if( addr == null ) throw new RuntimeException("Unhandled socket type: " + sad); // Read Inet from socket, port from the stream, figure out H2ONode _h2o = H2ONode.intern(addr, getPort()); _firstPage = true; assert _h2o != null; _persist = 0; // No persistance } /** Incoming TCP request. Make a read-mode AutoBuffer from the open Channel, * figure the originating H2ONode from the first few bytes read. * * remoteAddress set to null means that the communication is originating from non-h2o node, non-null value * represents the case where the communication is coming from h2o node. * */ AutoBuffer( ByteChannel sock, InetAddress remoteAddress ) throws IOException { _chan = sock; raisePriority(); // Make TCP priority high _bb = BBP_BIG.make(); // Get a big / TPC-sized ByteBuffer _bb.flip(); _read = true; // Reading by default _firstPage = true; // Read Inet from socket, port from the stream, figure out H2ONode if(remoteAddress!=null) { _h2o = H2ONode.intern(remoteAddress, getPort()); }else{ // In case the communication originates from non-h2o node, we set _h2o node to null. // It is done for 2 reasons: // - H2ONode.intern creates a new thread and if there's a lot of connections // from non-h2o environment, it could end up with too many open files exception. // - H2OIntern also reads port (getPort()) and additional information which we do not send // in communication originating from non-h2o nodes _h2o = null; } _firstPage = true; // Yes, must reset this. _time_start_ms = System.currentTimeMillis(); _persist = Value.TCP; } /** Make an AutoBuffer to write to an H2ONode. Requests for full buffer will * open a TCP socket and roll through writing to the target. Smaller * requests will send via UDP. Small requests get ordered by priority, so * that e.g. NACK and ACKACK messages have priority over most anything else. * This helps in UDP floods to shut down flooding senders. */ private byte _msg_priority; AutoBuffer( H2ONode h2o, byte priority ) { // If UDP goes via UDP, we write into a DBB up front - because we plan on // sending it out via a Datagram socket send call. If UDP goes via batched // TCP, we write into a HBB up front, because this will be copied again // into a large outgoing buffer. _bb = H2O.ARGS.useUDP // Actually use UDP? ? BBP_SML.make() // Make DirectByteBuffers to start with : ByteBuffer.wrap(new byte[16]).order(ByteOrder.nativeOrder()); _chan = null; // Channel made lazily only if we write alot _h2o = h2o; _read = false; // Writing by default _firstPage = true; // Filling first page assert _h2o != null; _time_start_ms = System.currentTimeMillis(); _persist = Value.TCP; _msg_priority = priority; } /** Spill-to/from-disk request. */ public AutoBuffer( FileChannel fc, boolean read, byte persist ) { _bb = BBP_BIG.make(); // Get a big / TPC-sized ByteBuffer _chan = fc; // Write to read/write _h2o = null; // File Channels never have an _h2o _read = read; // Mostly assert reading vs writing if( read ) _bb.flip(); _time_start_ms = System.currentTimeMillis(); _persist = persist; // One of Value.ICE, NFS, S3, HDFS } /** Read from UDP multicast. Same as the byte[]-read variant, except there is an H2O. */ AutoBuffer( DatagramPacket pack ) { _size = pack.getLength(); _bb = ByteBuffer.wrap(pack.getData(), 0, pack.getLength()).order(ByteOrder.nativeOrder()); _bb.position(0); _read = true; _firstPage = true; _chan = null; _h2o = H2ONode.intern(pack.getAddress(), getPort()); _persist = 0; // No persistance } /** Read from a UDP_TCP buffer; could be in the middle of a large buffer */ AutoBuffer( H2ONode h2o, byte[] buf, int off, int len ) { assert buf != null : "null fed to ByteBuffer.wrap"; _h2o = h2o; _bb = ByteBuffer.wrap(buf,off,len).order(ByteOrder.nativeOrder()); _chan = null; _read = true; _firstPage = true; _persist = 0; // No persistance _size = len; } /** Read from a fixed byte[]; should not be closed. */ public AutoBuffer( byte[] buf ) { this(null,buf,0, buf.length); } /** Write to an ever-expanding byte[]. Instead of calling {@link #close()}, * call {@link #buf()} to retrieve the final byte[]. */ public AutoBuffer( ) { _bb = ByteBuffer.wrap(new byte[16]).order(ByteOrder.nativeOrder()); _chan = null; _h2o = null; _read = false; _firstPage = true; _persist = 0; // No persistance } /** Write to a known sized byte[]. Instead of calling close(), call * {@link #bufClose()} to retrieve the final byte[]. */ public AutoBuffer( int len ) { _bb = ByteBuffer.wrap(MemoryManager.malloc1(len)).order(ByteOrder.nativeOrder()); _chan = null; _h2o = null; _read = false; _firstPage = true; _persist = 0; // No persistance } /** Write to a persistent Stream, including all TypeMap info to allow later * reloading (by the same exact rev of H2O). */ public AutoBuffer( OutputStream os, boolean persist ) { _bb = ByteBuffer.wrap(MemoryManager.malloc1(BBP_BIG._size)).order(ByteOrder.nativeOrder()); _read = false; _os = os; if( persist ) put1(0x1C).put1(0xED).putStr(H2O.ABV.projectVersion()).putAStr(TypeMap.CLAZZES); else put1(0); _chan = null; _h2o = null; _firstPage = true; _persist = 0; } /** Read from a persistent Stream (including all TypeMap info) into same * exact rev of H2O). */ public AutoBuffer( InputStream is ) { _chan = null; _h2o = null; _firstPage = true; _persist = 0; _read = true; _bb = ByteBuffer.wrap(MemoryManager.malloc1(BBP_BIG._size)).order(ByteOrder.nativeOrder()); _bb.flip(); _is = is; int b = get1U(); if( b==0 ) return; // No persistence info int magic = get1U(); if( b!=0x1C || magic != 0xED ) throw new IllegalArgumentException("Missing magic number 0x1CED at stream start"); String version = getStr(); if( !version.equals(H2O.ABV.projectVersion()) ) throw new IllegalArgumentException("Found version "+version+", but running version "+H2O.ABV.projectVersion()); String[] typeMap = getAStr(); _typeMap = new short[typeMap.length]; for( int i=0; i<typeMap.length; i++ ) _typeMap[i] = (short)(typeMap[i]==null ? 0 : TypeMap.onIce(typeMap[i])); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("[AB ").append(_read ? "read " : "write "); sb.append(_firstPage?"first ":"2nd ").append(_h2o); sb.append(" ").append(Value.nameOfPersist(_persist)); if( _bb != null ) sb.append(" 0 <= ").append(_bb.position()).append(" <= ").append(_bb.limit()); if( _bb != null ) sb.append(" <= ").append(_bb.capacity()); return sb.append("]").toString(); } // Fetch a DBB from an object pool... they are fairly expensive to make // because a native call is required to get the backing memory. I've // included BB count tracking code to help track leaks. As of 12/17/2012 the // leaks are under control, but figure this may happen again so keeping these // counters around. // // We use 2 pool sizes: lots of small UDP packet-sized buffers and fewer // larger TCP-sized buffers. private static final boolean DEBUG = Boolean.getBoolean("h2o.find-ByteBuffer-leaks"); private static long HWM=0; static class BBPool { long _made, _cached, _freed; long _numer, _denom, _goal=4*H2O.NUMCPUS, _lastGoal; final ArrayList<ByteBuffer> _bbs = new ArrayList<>(); final int _size; // Big or small size of ByteBuffers BBPool( int sz) { _size=sz; } private ByteBuffer stats( ByteBuffer bb ) { if( !DEBUG ) return bb; if( ((_made+_cached)&255)!=255 ) return bb; // Filter printing to 1 in 256 long now = System.currentTimeMillis(); if( now < HWM ) return bb; HWM = now+1000; water.util.SB sb = new water.util.SB(); sb.p("BB").p(this==BBP_BIG?1:0).p(" made=").p(_made).p(" -freed=").p(_freed).p(", cache hit=").p(_cached).p(" ratio=").p(_numer/_denom).p(", goal=").p(_goal).p(" cache size=").p(_bbs.size()).nl(); for( int i=0; i<H2O.MAX_PRIORITY; i++ ) { int x = H2O.getWrkQueueSize(i); if( x > 0 ) sb.p('Q').p(i).p('=').p(x).p(' '); } Log.warn(sb.nl().toString()); return bb; } ByteBuffer make() { while( true ) { // Repeat loop for DBB OutOfMemory errors ByteBuffer bb=null; synchronized(_bbs) { int sz = _bbs.size(); if( sz > 0 ) { bb = _bbs.remove(sz-1); _cached++; _numer++; } } if( bb != null ) return stats(bb); // Cache empty; go get one from C/Native memory try { bb = ByteBuffer.allocateDirect(_size).order(ByteOrder.nativeOrder()); synchronized(this) { _made++; _denom++; _goal = Math.max(_goal,_made-_freed); _lastGoal=System.nanoTime(); } // Goal was too low, raise it return stats(bb); } catch( OutOfMemoryError oome ) { // java.lang.OutOfMemoryError: Direct buffer memory if( !"Direct buffer memory".equals(oome.getMessage()) ) throw oome; System.out.println("OOM DBB - Sleeping & retrying"); try { Thread.sleep(100); } catch( InterruptedException ignore ) { } } } } void free(ByteBuffer bb) { // Heuristic: keep the ratio of BB's made to cache-hits at a fixed level. // Free to GC if ratio is high, free to internal cache if low. long ratio = _numer/(_denom+1); synchronized(_bbs) { if( ratio < 100 || _bbs.size() < _goal ) { // low hit/miss ratio or below goal bb.clear(); // Clear-before-add _bbs.add(bb); } else _freed++; // Toss the extras (above goal & ratio) long now = System.nanoTime(); if( now-_lastGoal > 1000000000L ) { // Once/sec, drop goal by 10% _lastGoal = now; if( ratio > 110 ) // If ratio is really high, lower goal _goal=Math.max(4*H2O.NUMCPUS,(long)(_goal*0.99)); // Once/sec, lower numer/denom... means more recent activity outweighs really old stuff long denom = (long) (0.99 * _denom); // Proposed reduction if( denom > 10 ) { // Keep a little precision _numer = (long) (0.99 * _numer); // Keep ratio between made & cached the same _denom = denom; // ... by lowering both by 10% } } } } static int FREE( ByteBuffer bb ) { if(bb.isDirect()) (bb.capacity()==BBP_BIG._size ? BBP_BIG : BBP_SML).free(bb); return 0; // Flow coding } } static BBPool BBP_SML = new BBPool( 2*1024); // Bytebuffer "common small size", for UDP static BBPool BBP_BIG = new BBPool(64*1024); // Bytebuffer "common big size", for TCP public static int TCP_BUF_SIZ = BBP_BIG._size; private int bbFree() { if(_bb != null && _bb.isDirect()) BBPool.FREE(_bb); _bb = null; return 0; // Flow-coding } // You thought TCP was a reliable protocol, right? WRONG! Fails 100% of the // time under heavy network load. Connection-reset-by-peer & connection // timeouts abound, even after a socket open and after a 1st successful // ByteBuffer write. It *appears* that the reader is unaware that a writer // was told "go ahead and write" by the TCP stack, so all these fails are // only on the writer-side. public static class AutoBufferException extends RuntimeException { public final IOException _ioe; AutoBufferException( IOException ioe ) { _ioe = ioe; } } // For reads, just assert all was read and close and release resources. // (release ByteBuffer back to the common pool). For writes, force any final // bytes out. If the write is to an H2ONode and is short, send via UDP. // AutoBuffer close calls order; i.e. a reader close() will block until the // writer does a close(). public final int close() { //if( _size > 2048 ) System.out.println("Z="+_zeros+" / "+_size+", A="+_arys); if( isClosed() ) return 0; // Already closed assert _h2o != null || _chan != null || _os != null || _is != null; // Byte-array backed should not be closed try { if( _chan == null ) { // No channel? if( _read ) { if( _is != null ) _is.close(); return 0; } else { // Write if( _os != null ) { // Final stream write bits _os.write(_bb.array(),0,_bb.position()); _os.close(); return 0; } else { // For small-packet write, send via UDP. Since nothing is sent until // now, this close() call trivially orders - since the reader will not // even start (much less close()) until this packet is sent. if( _bb.position() < MTU) return udpSend(); } // oops - Big Write, switch to TCP and finish out there } } // Force AutoBuffer 'close' calls to order; i.e. block readers until // writers do a 'close' - by writing 1 more byte in the close-call which // the reader will have to wait for. if( hasTCP()) { // TCP connection? try { if( _read ) { // Reader? int x = get1U(); // Read 1 more byte assert x == 0xab : "AB.close instead of 0xab sentinel got "+x+", "+this; assert _chan != null; // chan set by incoming reader, since we KNOW it is a TCP // Write the reader-handshake-byte. SocketChannelUtils.underlyingSocketChannel(_chan).socket().getOutputStream().write(0xcd); // do not close actually reader socket; recycle it in TCPReader thread } else { // Writer? put1(0xab); // Write one-more byte ; might set _chan from null to not-null sendPartial(); // Finish partial writes; might set _chan from null to not-null assert _chan != null; // _chan is set not-null now! // Read the writer-handshake-byte. int x = SocketChannelUtils.underlyingSocketChannel(_chan).socket().getInputStream().read(); // either TCP con was dropped or other side closed connection without reading/confirming (e.g. task was cancelled). if( x == -1 ) throw new IOException("Other side closed connection before handshake byte read"); assert x == 0xcd : "Handshake; writer expected a 0xcd from reader but got "+x; } } catch( IOException ioe ) { try { _chan.close(); } catch( IOException ignore ) {} // Silently close _chan = null; // No channel now, since i/o error throw ioe; // Rethrow after close } finally { if( !_read ) _h2o.freeTCPSocket(_chan); // Recycle writable TCP channel restorePriority(); // And if we raised priority, lower it back } } else { // FileChannel if( !_read ) sendPartial(); // Finish partial file-system writes _chan.close(); _chan = null; // Closed file channel } } catch( IOException e ) { // Dunno how to handle so crash-n-burn throw new AutoBufferException(e); } finally { bbFree(); _time_close_ms = System.currentTimeMillis(); // TimeLine.record_IOclose(this,_persist); // Profile AutoBuffer connections assert isClosed(); } return 0; } // Need a sock for a big read or write operation. // See if we got one already, else open a new socket. private void tcpOpen() throws IOException { assert _firstPage && _bb.limit() >= 1+2+4; // At least something written assert _chan == null; // assert _bb.position()==0; _chan = _h2o.getTCPSocket(); raisePriority(); } // Just close the channel here without reading anything. Without the task // object at hand we do not know what (how many bytes) should we read from // the channel. And since the other side will try to read confirmation from // us before closing the channel, we can not read till the end. So we just // close the channel and let the other side to deal with it and figure out // the task has been cancelled (still sending ack ack back). void drainClose() { if( isClosed() ) return; // Already closed final ByteChannel chan = _chan; // Read before closing assert _h2o != null || chan != null; // Byte-array backed should not be closed if( chan != null ) { // Channel assumed sick from prior IOException try { chan.close(); } catch( IOException ignore ) {} // Silently close _chan = null; // No channel now! if( !_read && SocketChannelUtils.isSocketChannel(chan)) _h2o.freeTCPSocket(chan); // Recycle writable TCP channel } restorePriority(); // And if we raised priority, lower it back bbFree(); _time_close_ms = System.currentTimeMillis(); // TimeLine.record_IOclose(this,_persist); // Profile AutoBuffer connections assert isClosed(); } // True if we opened a TCP channel, or will open one to close-and-send boolean hasTCP() { assert !isClosed(); return SocketChannelUtils.isSocketChannel(_chan) || (_h2o!=null && _bb.position() >= MTU); } // Size in bytes sent, after a close() int size() { return _size; } //int zeros() { return _zeros; } public int position () { return _bb.position(); } public AutoBuffer position(int p) {_bb.position(p); return this;} /** Skip over some bytes in the byte buffer. Caller is responsible for not * reading off end of the bytebuffer; generally this is easy for * array-backed autobuffers and difficult for i/o-backed bytebuffers. */ public void skip(int skip) { _bb.position(_bb.position()+skip); } // Return byte[] from a writable AutoBuffer public final byte[] buf() { assert _h2o==null && _chan==null && !_read && !_bb.isDirect(); return MemoryManager.arrayCopyOfRange(_bb.array(), _bb.arrayOffset(), _bb.position()); } public final byte[] bufClose() { byte[] res = _bb.array(); bbFree(); return res; } // For TCP sockets ONLY, raise the thread priority. We assume we are // blocking other Nodes with our network I/O, so try to get the I/O // over with. private void raisePriority() { if(_oldPrior == -1){ assert SocketChannelUtils.isSocketChannel(_chan); _oldPrior = Thread.currentThread().getPriority(); Thread.currentThread().setPriority(Thread.MAX_PRIORITY-1); } } private void restorePriority() { if( _oldPrior == -1 ) return; Thread.currentThread().setPriority(_oldPrior); _oldPrior = -1; } // Send via UDP socket. Unlike eg TCP sockets, we only need one for sending // so we keep a global one. Also, we do not close it when done, and we do // not connect it up-front to a target - but send the entire packet right now. private int udpSend() throws IOException { assert _chan == null; TimeLine.record_send(this,false); _size = _bb.position(); assert _size < AutoBuffer.BBP_SML._size; _bb.flip(); // Flip for sending if( _h2o==H2O.SELF ) { // SELF-send is the multi-cast signal water.init.NetworkInit.multicast(_bb, _msg_priority); } else { // Else single-cast send if(H2O.ARGS.useUDP) // Send via UDP directly water.init.NetworkInit.CLOUD_DGRAM.send(_bb, _h2o._key); else // Send via bulk TCP _h2o.sendMessage(_bb, _msg_priority); } return 0; // Flow-coding } // Flip to write-mode AutoBuffer clearForWriting(byte priority) { assert _read; _read = false; _msg_priority = priority; _bb.clear(); _firstPage = true; return this; } // Flip to read-mode public AutoBuffer flipForReading() { assert !_read; _read = true; _bb.flip(); _firstPage = true; return this; } /** Ensure the buffer has space for sz more bytes */ private ByteBuffer getSp( int sz ) { return sz > _bb.remaining() ? getImpl(sz) : _bb; } /** Ensure buffer has at least sz bytes in it. * - Also, set position just past this limit for future reading. */ private ByteBuffer getSz(int sz) { assert _firstPage : "getSz() is only valid for early UDP bytes"; if( sz > _bb.limit() ) getImpl(sz); _bb.position(sz); return _bb; } private ByteBuffer getImpl( int sz ) { assert _read : "Reading from a buffer in write mode"; _bb.compact(); // Move remaining unread bytes to start of buffer; prep for reading // Its got to fit or we asked for too much assert _bb.position()+sz <= _bb.capacity() : "("+_bb.position()+"+"+sz+" <= "+_bb.capacity()+")"; long ns = System.nanoTime(); while( _bb.position() < sz ) { // Read until we got enuf try { int res = readAnInt(); // Read more // Readers are supposed to be strongly typed and read the exact expected bytes. // However, if a TCP connection fails mid-read we'll get a short-read. // This is indistinguishable from a mis-alignment between the writer and reader! if( res <= 0 ) throw new AutoBufferException(new EOFException("Reading "+sz+" bytes, AB="+this)); if( _is != null ) _bb.position(_bb.position()+res); // Advance BB for Streams manually _size += res; // What we read } catch( IOException e ) { // Dunno how to handle so crash-n-burn // Linux/Ubuntu message for a reset-channel if( e.getMessage().equals("An existing connection was forcibly closed by the remote host") ) throw new AutoBufferException(e); // Windows message for a reset-channel if( e.getMessage().equals("An established connection was aborted by the software in your host machine") ) throw new AutoBufferException(e); throw Log.throwErr(e); } } _time_io_ns += (System.nanoTime()-ns); _bb.flip(); // Prep for handing out bytes //for( int i=0; i < _bb.limit(); i++ ) if( _bb.get(i)==0 ) _zeros++; _firstPage = false; // First page of data is gone gone gone return _bb; } private int readAnInt() throws IOException { if (_is == null) return _chan.read(_bb); final byte[] array = _bb.array(); final int position = _bb.position(); final int remaining = _bb.remaining(); try { return _is.read(array, position, remaining); } catch (IOException ioe) { throw new IOException("Failed reading " + remaining + " bytes into buffer[" + array.length + "] at " + position + " from " + sourceName + " " + _is, ioe); } } /** Put as needed to keep from overflowing the ByteBuffer. */ private ByteBuffer putSp( int sz ) { assert !_read; while (sz > _bb.remaining()) { if ((_h2o==null && _chan == null) || (_bb.hasArray() && _bb.capacity() < BBP_BIG._size)) expandByteBuffer(sz); else sendPartial(); } return _bb; } // Do something with partial results, because the ByteBuffer is full. // If we are doing I/O, ship the bytes we have now and flip the ByteBuffer. private ByteBuffer sendPartial() { // Doing I/O with the full ByteBuffer - ship partial results _size += _bb.position(); if( _chan == null ) TimeLine.record_send(this, true); _bb.flip(); // Prep for writing. try { if( _chan == null ) tcpOpen(); // This is a big operation. Open a TCP socket as-needed. //for( int i=0; i < _bb.limit(); i++ ) if( _bb.get(i)==0 ) _zeros++; long ns = System.nanoTime(); while( _bb.hasRemaining() ) { _chan.write(_bb); if( RANDOM_TCP_DROP != null && SocketChannelUtils.isSocketChannel(_chan) && RANDOM_TCP_DROP.nextInt(100) == 0 ) throw new IOException("Random TCP Write Fail"); } _time_io_ns += (System.nanoTime()-ns); } catch( IOException e ) { // Some kind of TCP fail? // Change to an unchecked exception (so we don't have to annotate every // frick'n put1/put2/put4/read/write call). Retry & recovery happens at // a higher level. AutoBuffers are used for many things including e.g. // disk i/o & UDP writes; this exception only happens on a failed TCP // write - and we don't want to make the other AutoBuffer users have to // declare (and then ignore) this exception. throw new AutoBufferException(e); } _firstPage = false; _bb.clear(); return _bb; } // Called when the byte buffer doesn't have enough room // If buffer is array backed, and the needed room is small, // increase the size of the backing array, // otherwise dump into a large direct buffer private ByteBuffer expandByteBuffer(int sizeHint) { long needed = (long) sizeHint - _bb.remaining() + _bb.capacity(); // Max needed is 2G if (needed > Integer.MAX_VALUE) { throw new IllegalArgumentException("Cannot allocate more than 2GB array: sizeHint="+sizeHint+", " + "needed="+needed + ", bb.remaining()=" + _bb.remaining() + ", bb.capacity()="+_bb.capacity()); } if ((_h2o==null && _chan == null) || (_bb.hasArray() && needed < MTU)) { byte[] ary = _bb.array(); // just get twice what is currently needed but not more then max array size (2G) // Be careful not to overflow because of integer math! int newLen = (int) Math.min(1L << (water.util.MathUtils.log2(needed)+1), Integer.MAX_VALUE - 1L); newLen = Math.min(Integer.MAX_VALUE-100, newLen); // hard stop just below 32bit limit int oldpos = _bb.position(); _bb = ByteBuffer.wrap(MemoryManager.arrayCopyOfRange(ary,0,newLen),oldpos,newLen-oldpos) .order(ByteOrder.nativeOrder()); } else if (_bb.capacity() != BBP_BIG._size) { //avoid expanding existing BBP items int oldPos = _bb.position(); _bb.flip(); _bb = BBP_BIG.make().put(_bb); _bb.position(oldPos); } return _bb; } @SuppressWarnings("unused") public String getStr(int off, int len) { return new String(_bb.array(), _bb.arrayOffset()+off, len, UTF_8); } // ----------------------------------------------- // Utility functions to get various Java primitives @SuppressWarnings("unused") public boolean getZ() { return get1()!=0; } @SuppressWarnings("unused") public byte get1 () { return getSp(1).get (); } @SuppressWarnings("unused") public int get1U() { return get1() & 0xFF; } @SuppressWarnings("unused") public char get2 () { return getSp(2).getChar (); } @SuppressWarnings("unused") public short get2s () { return getSp(2).getShort (); } @SuppressWarnings("unused") public int get3 () { getSp(3); return get1U() | get1U() << 8 | get1U() << 16; } @SuppressWarnings("unused") public int get4 () { return getSp(4).getInt (); } @SuppressWarnings("unused") public float get4f() { return getSp(4).getFloat (); } @SuppressWarnings("unused") public long get8 () { return getSp(8).getLong (); } @SuppressWarnings("unused") public double get8d() { return getSp(8).getDouble(); } int get1U(int off) { return _bb.get (off)&0xFF; } int get4 (int off) { return _bb.getInt (off); } long get8 (int off) { return _bb.getLong(off); } @SuppressWarnings("unused") public AutoBuffer putZ (boolean b){ return put1(b?1:0); } @SuppressWarnings("unused") public AutoBuffer put1 ( int b) { assert b >= -128 && b <= 255 : ""+b+" is not a byte"; putSp(1).put((byte)b); return this; } @SuppressWarnings("unused") public AutoBuffer put2 ( char c) { putSp(2).putChar (c); return this; } @SuppressWarnings("unused") public AutoBuffer put2 ( short s) { putSp(2).putShort (s); return this; } @SuppressWarnings("unused") public AutoBuffer put2s ( short s) { return put2(s); } @SuppressWarnings("unused") public AutoBuffer put3( int x ) { assert (-1<<24) <= x && x < (1<<24); return put1((x)&0xFF).put1((x >> 8)&0xFF).put1(x >> 16); } @SuppressWarnings("unused") public AutoBuffer put4 ( int i) { putSp(4).putInt (i); return this; } @SuppressWarnings("unused") public AutoBuffer put4f( float f) { putSp(4).putFloat (f); return this; } @SuppressWarnings("unused") public AutoBuffer put8 ( long l) { putSp(8).putLong (l); return this; } @SuppressWarnings("unused") public AutoBuffer put8d(double d) { putSp(8).putDouble(d); return this; } public AutoBuffer put(Freezable f) { if( f == null ) return putInt(TypeMap.NULL); assert f.frozenType() > 0 : "No TypeMap for "+f.getClass().getName(); putInt(f.frozenType()); return f.write(this); } public <T extends Freezable> T get() { int id = getInt(); if( id == TypeMap.NULL ) return null; if( _is!=null ) id = _typeMap[id]; return (T)TypeMap.newFreezable(id).read(this); } public <T extends Freezable> T get(Class<T> tc) { int id = getInt(); if( id == TypeMap.NULL ) return null; if( _is!=null ) id = _typeMap[id]; assert tc.isInstance(TypeMap.theFreezable(id)):tc.getName() + " != " + TypeMap.theFreezable(id).getClass().getName() + ", id = " + id; return (T)TypeMap.newFreezable(id).read(this); } // Write Key's target IFF the Key is not null; target can be null. public AutoBuffer putKey(Key k) { if( k==null ) return this; // Key is null ==> write nothing Keyed kd = DKV.getGet(k); put(kd); return kd == null ? this : kd.writeAll_impl(this); } public Keyed getKey(Key k, Futures fs) { return k==null ? null : getKey(fs); // Key is null ==> read nothing } public Keyed getKey(Futures fs) { Keyed kd = get(Keyed.class); if( kd == null ) return null; DKV.put(kd,fs); return kd.readAll_impl(this,fs); } // Put a (compressed) integer. Specifically values in the range -1 to ~250 // will take 1 byte, values near a Short will take 1+2 bytes, values near an // Int will take 1+4 bytes, and bigger values 1+8 bytes. This compression is // optimized for small integers (including -1 which is often used as a "array // is null" flag when passing the array length). public AutoBuffer putInt(int x) { if( 0 <= (x+1)&& (x+1) <= 253 ) return put1(x+1); if( Short.MIN_VALUE <= x && x <= Short.MAX_VALUE ) return put1(255).put2((short)x); return put1(254).put4(x); } // Get a (compressed) integer. See above for the compression strategy and reasoning. int getInt( ) { int x = get1U(); if( x <= 253 ) return x-1; if( x==255 ) return (short)get2(); assert x==254; return get4(); } // Put a zero-compressed array. Compression is: // If null : putInt(-1) // Else // putInt(# of leading nulls) // putInt(# of non-nulls) // If # of non-nulls is > 0, putInt( # of trailing nulls) long putZA( Object[] A ) { if( A==null ) { putInt(-1); return 0; } int x=0; for( ; x<A.length; x++ ) if( A[x ]!=null ) break; int y=A.length; for( ; y>x; y-- ) if( A[y-1]!=null ) break; putInt(x); // Leading zeros to skip putInt(y-x); // Mixed non-zero guts in middle if( y > x ) // If any trailing nulls putInt(A.length-y); // Trailing zeros return ((long)x<<32)|(y-x); // Return both leading zeros, and middle non-zeros } // Get the lengths of a zero-compressed array. // Returns -1 if null. // Returns a long of (leading zeros | middle non-zeros). // If there are non-zeros, caller has to read the trailing zero-length. long getZA( ) { int x=getInt(); // Length of leading zeros if( x == -1 ) return -1; // or a null int nz=getInt(); // Non-zero in the middle return ((long)x<<32)|(long)nz; // Return both ints } // TODO: untested. . . @SuppressWarnings("unused") public AutoBuffer putAEnum(Enum[] enums) { //_arys++; long xy = putZA(enums); if( xy == -1 ) return this; int x=(int)(xy>>32); int y=(int)xy; for( int i=x; i<x+y; i++ ) putEnum(enums[i]); return this; } @SuppressWarnings("unused") public <E extends Enum> E[] getAEnum(E[] values) { //_arys++; long xy = getZA(); if( xy == -1 ) return null; int x=(int)(xy>>32); // Leading nulls int y=(int)xy; // Middle non-zeros int z = y==0 ? 0 : getInt(); // Trailing nulls E[] ts = (E[]) Array.newInstance(values.getClass().getComponentType(), x+y+z); for( int i = x; i < x+y; ++i ) ts[i] = getEnum(values); return ts; } @SuppressWarnings("unused") public AutoBuffer putA(Freezable[] fs) { //_arys++; long xy = putZA(fs); if( xy == -1 ) return this; int x=(int)(xy>>32); int y=(int)xy; for( int i=x; i<x+y; i++ ) put(fs[i]); return this; } public AutoBuffer putAA(Freezable[][] fs) { //_arys++; long xy = putZA(fs); if( xy == -1 ) return this; int x=(int)(xy>>32); int y=(int)xy; for( int i=x; i<x+y; i++ ) putA(fs[i]); return this; } @SuppressWarnings("unused") public AutoBuffer putAAA(Freezable[][][] fs) { //_arys++; long xy = putZA(fs); if( xy == -1 ) return this; int x=(int)(xy>>32); int y=(int)xy; for( int i=x; i<x+y; i++ ) putAA(fs[i]); return this; } public <T extends Freezable> T[] getA(Class<T> tc) { //_arys++; long xy = getZA(); if( xy == -1 ) return null; int x=(int)(xy>>32); // Leading nulls int y=(int)xy; // Middle non-zeros int z = y==0 ? 0 : getInt(); // Trailing nulls T[] ts = (T[]) Array.newInstance(tc, x+y+z); for( int i = x; i < x+y; ++i ) ts[i] = get(tc); return ts; } public <T extends Freezable> T[][] getAA(Class<T> tc) { //_arys++; long xy = getZA(); if( xy == -1 ) return null; int x=(int)(xy>>32); // Leading nulls int y=(int)xy; // Middle non-zeros int z = y==0 ? 0 : getInt(); // Trailing nulls Class<T[]> tcA = (Class<T[]>) Array.newInstance(tc, 0).getClass(); T[][] ts = (T[][]) Array.newInstance(tcA, x+y+z); for( int i = x; i < x+y; ++i ) ts[i] = getA(tc); return ts; } @SuppressWarnings("unused") public <T extends Freezable> T[][][] getAAA(Class<T> tc) { //_arys++; long xy = getZA(); if( xy == -1 ) return null; int x=(int)(xy>>32); // Leading nulls int y=(int)xy; // Middle non-zeros int z = y==0 ? 0 : getInt(); // Trailing nulls Class<T[] > tcA = (Class<T[] >) Array.newInstance(tc , 0).getClass(); Class<T[][]> tcAA = (Class<T[][]>) Array.newInstance(tcA, 0).getClass(); T[][][] ts = (T[][][]) Array.newInstance(tcAA, x+y+z); for( int i = x; i < x+y; ++i ) ts[i] = getAA(tc); return ts; } public AutoBuffer putAStr(String[] fs) { //_arys++; long xy = putZA(fs); if( xy == -1 ) return this; int x=(int)(xy>>32); int y=(int)xy; for( int i=x; i<x+y; i++ ) putStr(fs[i]); return this; } public String[] getAStr() { //_arys++; long xy = getZA(); if( xy == -1 ) return null; int x=(int)(xy>>32); // Leading nulls int y=(int)xy; // Middle non-zeros int z = y==0 ? 0 : getInt(); // Trailing nulls String[] ts = new String[x+y+z]; for( int i = x; i < x+y; ++i ) ts[i] = getStr(); return ts; } @SuppressWarnings("unused") public AutoBuffer putAAStr(String[][] fs) { //_arys++; long xy = putZA(fs); if( xy == -1 ) return this; int x=(int)(xy>>32); int y=(int)xy; for( int i=x; i<x+y; i++ ) putAStr(fs[i]); return this; } @SuppressWarnings("unused") public String[][] getAAStr() { //_arys++; long xy = getZA(); if( xy == -1 ) return null; int x=(int)(xy>>32); // Leading nulls int y=(int)xy; // Middle non-zeros int z = y==0 ? 0 : getInt(); // Trailing nulls String[][] ts = new String[x+y+z][]; for( int i = x; i < x+y; ++i ) ts[i] = getAStr(); return ts; } // Read the smaller of _bb.remaining() and len into buf. // Return bytes read, which could be zero. int read( byte[] buf, int off, int len ) { int sz = Math.min(_bb.remaining(),len); _bb.get(buf,off,sz); return sz; } // ----------------------------------------------- // Utility functions to handle common UDP packet tasks. // Get the 1st control byte int getCtrl( ) { return getSz(1).get(0)&0xFF; } // Get the port in next 2 bytes int getPort( ) { return getSz(1+2).getChar(1); } // Get the task# in the next 4 bytes int getTask( ) { return getSz(1+2+4).getInt(1+2); } // Get the flag in the next 1 byte int getFlag( ) { return getSz(1+2+4+1).get(1+2+4); } // Set the ctrl, port, task. Ready to write more bytes afterwards AutoBuffer putUdp (UDP.udp type) { assert _bb.position() == 0; putSp(_bb.position()+1+2); _bb.put ((byte)type.ordinal()); _bb.putChar((char)H2O.H2O_PORT ); // Outgoing port is always the sender's (me) port return this; } AutoBuffer putTask(UDP.udp type, int tasknum) { return putUdp(type).put4(tasknum); } AutoBuffer putTask(int ctrl, int tasknum) { assert _bb.position() == 0; putSp(_bb.position()+1+2+4); _bb.put((byte)ctrl).putChar((char)H2O.H2O_PORT).putInt(tasknum); return this; } // ----------------------------------------------- // Utility functions to read & write arrays public boolean[] getAZ() { int len = getInt(); if (len == -1) return null; boolean[] r = new boolean[len]; for (int i=0;i<len;++i) r[i] = getZ(); return r; } public byte[] getA1( ) { //_arys++; int len = getInt(); return len == -1 ? null : getA1(len); } public byte[] getA1( int len ) { byte[] buf = MemoryManager.malloc1(len); int sofar = 0; while( sofar < len ) { int more = Math.min(_bb.remaining(), len - sofar); _bb.get(buf, sofar, more); sofar += more; if( sofar < len ) getSp(Math.min(_bb.capacity(), len-sofar)); } return buf; } public short[] getA2( ) { //_arys++; int len = getInt(); if( len == -1 ) return null; short[] buf = MemoryManager.malloc2(len); int sofar = 0; while( sofar < buf.length ) { ShortBuffer as = _bb.asShortBuffer(); int more = Math.min(as.remaining(), len - sofar); as.get(buf, sofar, more); sofar += more; _bb.position(_bb.position() + as.position()*2); if( sofar < len ) getSp(Math.min(_bb.capacity()-1, (len-sofar)*2)); } return buf; } public int[] getA4( ) { //_arys++; int len = getInt(); if( len == -1 ) return null; int[] buf = MemoryManager.malloc4(len); int sofar = 0; while( sofar < buf.length ) { IntBuffer as = _bb.asIntBuffer(); int more = Math.min(as.remaining(), len - sofar); as.get(buf, sofar, more); sofar += more; _bb.position(_bb.position() + as.position()*4); if( sofar < len ) getSp(Math.min(_bb.capacity()-3, (len-sofar)*4)); } return buf; } public float[] getA4f( ) { //_arys++; int len = getInt(); if( len == -1 ) return null; float[] buf = MemoryManager.malloc4f(len); int sofar = 0; while( sofar < buf.length ) { FloatBuffer as = _bb.asFloatBuffer(); int more = Math.min(as.remaining(), len - sofar); as.get(buf, sofar, more); sofar += more; _bb.position(_bb.position() + as.position()*4); if( sofar < len ) getSp(Math.min(_bb.capacity()-3, (len-sofar)*4)); } return buf; } public long[] getA8( ) { //_arys++; // Get the lengths of lead & trailing zero sections, and the non-zero // middle section. int x = getInt(); if( x == -1 ) return null; int y = getInt(); // Non-zero in the middle int z = y==0 ? 0 : getInt();// Trailing zeros long[] buf = MemoryManager.malloc8(x+y+z); switch( get1U() ) { // 1,2,4 or 8 for how the middle section is passed case 1: for( int i=x; i<x+y; i++ ) buf[i] = get1U(); return buf; case 2: for( int i=x; i<x+y; i++ ) buf[i] = (short)get2(); return buf; case 4: for( int i=x; i<x+y; i++ ) buf[i] = get4(); return buf; case 8: break; default: throw H2O.fail(); } int sofar = x; while( sofar < x+y ) { LongBuffer as = _bb.asLongBuffer(); int more = Math.min(as.remaining(), x+y - sofar); as.get(buf, sofar, more); sofar += more; _bb.position(_bb.position() + as.position()*8); if( sofar < x+y ) getSp(Math.min(_bb.capacity()-7, (x+y-sofar)*8)); } return buf; } public double[] getA8d( ) { //_arys++; int len = getInt(); if( len == -1 ) return null; double[] buf = MemoryManager.malloc8d(len); int sofar = 0; while( sofar < len ) { DoubleBuffer as = _bb.asDoubleBuffer(); int more = Math.min(as.remaining(), len - sofar); as.get(buf, sofar, more); sofar += more; _bb.position(_bb.position() + as.position()*8); if( sofar < len ) getSp(Math.min(_bb.capacity()-7, (len-sofar)*8)); } return buf; } @SuppressWarnings("unused") public byte[][] getAA1( ) { //_arys++; long xy = getZA(); if( xy == -1 ) return null; int x=(int)(xy>>32); // Leading nulls int y=(int)xy; // Middle non-zeros int z = y==0 ? 0 : getInt(); // Trailing nulls byte[][] ary = new byte[x+y+z][]; for( int i=x; i<x+y; i++ ) ary[i] = getA1(); return ary; } @SuppressWarnings("unused") public short[][] getAA2( ) { //_arys++; long xy = getZA(); if( xy == -1 ) return null; int x=(int)(xy>>32); // Leading nulls int y=(int)xy; // Middle non-zeros int z = y==0 ? 0 : getInt(); // Trailing nulls short[][] ary = new short[x+y+z][]; for( int i=x; i<x+y; i++ ) ary[i] = getA2(); return ary; } public int[][] getAA4( ) { //_arys++; long xy = getZA(); if( xy == -1 ) return null; int x=(int)(xy>>32); // Leading nulls int y=(int)xy; // Middle non-zeros int z = y==0 ? 0 : getInt(); // Trailing nulls int[][] ary = new int[x+y+z][]; for( int i=x; i<x+y; i++ ) ary[i] = getA4(); return ary; } @SuppressWarnings("unused") public float[][] getAA4f( ) { //_arys++; long xy = getZA(); if( xy == -1 ) return null; int x=(int)(xy>>32); // Leading nulls int y=(int)xy; // Middle non-zeros int z = y==0 ? 0 : getInt(); // Trailing nulls float[][] ary = new float[x+y+z][]; for( int i=x; i<x+y; i++ ) ary[i] = getA4f(); return ary; } public long[][] getAA8( ) { //_arys++; long xy = getZA(); if( xy == -1 ) return null; int x=(int)(xy>>32); // Leading nulls int y=(int)xy; // Middle non-zeros int z = y==0 ? 0 : getInt(); // Trailing nulls long[][] ary = new long[x+y+z][]; for( int i=x; i<x+y; i++ ) ary[i] = getA8(); return ary; } @SuppressWarnings("unused") public double[][] getAA8d( ) { //_arys++; long xy = getZA(); if( xy == -1 ) return null; int x=(int)(xy>>32); // Leading nulls int y=(int)xy; // Middle non-zeros int z = y==0 ? 0 : getInt(); // Trailing nulls double[][] ary = new double[x+y+z][]; for( int i=x; i<x+y; i++ ) ary[i] = getA8d(); return ary; } @SuppressWarnings("unused") public int[][][] getAAA4( ) { //_arys++; long xy = getZA(); if( xy == -1 ) return null; int x=(int)(xy>>32); // Leading nulls int y=(int)xy; // Middle non-zeros int z = y==0 ? 0 : getInt(); // Trailing nulls int[][][] ary = new int[x+y+z][][]; for( int i=x; i<x+y; i++ ) ary[i] = getAA4(); return ary; } @SuppressWarnings("unused") public long[][][] getAAA8( ) { //_arys++; long xy = getZA(); if( xy == -1 ) return null; int x=(int)(xy>>32); // Leading nulls int y=(int)xy; // Middle non-zeros int z = y==0 ? 0 : getInt(); // Trailing nulls long[][][] ary = new long[x+y+z][][]; for( int i=x; i<x+y; i++ ) ary[i] = getAA8(); return ary; } public double[][][] getAAA8d( ) { //_arys++; long xy = getZA(); if( xy == -1 ) return null; int x=(int)(xy>>32); // Leading nulls int y=(int)xy; // Middle non-zeros int z = y==0 ? 0 : getInt(); // Trailing nulls double[][][] ary = new double[x+y+z][][]; for( int i=x; i<x+y; i++ ) ary[i] = getAA8d(); return ary; } public String getStr( ) { int len = getInt(); return len == -1 ? null : new String(getA1(len), UTF_8); } public <E extends Enum> E getEnum(E[] values ) { int idx = get1(); return idx == -1 ? null : values[idx]; } public AutoBuffer putAZ( boolean[] ary ) { if( ary == null ) return putInt(-1); putInt(ary.length); for (boolean anAry : ary) putZ(anAry); return this; } public AutoBuffer putA1( byte[] ary ) { //_arys++; if( ary == null ) return putInt(-1); putInt(ary.length); return putA1(ary,ary.length); } public AutoBuffer putA1( byte[] ary, int length ) { return putA1(ary,0,length); } public AutoBuffer putA1( byte[] ary, int sofar, int length ) { if (length - sofar > _bb.remaining()) expandByteBuffer(length-sofar); while( sofar < length ) { int len = Math.min(length - sofar, _bb.remaining()); _bb.put(ary, sofar, len); sofar += len; if( sofar < length ) sendPartial(); } return this; } AutoBuffer putA2( short[] ary ) { //_arys++; if( ary == null ) return putInt(-1); putInt(ary.length); if (ary.length*2 > _bb.remaining()) expandByteBuffer(ary.length*2); int sofar = 0; while( sofar < ary.length ) { ShortBuffer sb = _bb.asShortBuffer(); int len = Math.min(ary.length - sofar, sb.remaining()); sb.put(ary, sofar, len); sofar += len; _bb.position(_bb.position() + sb.position()*2); if( sofar < ary.length ) sendPartial(); } return this; } public AutoBuffer putA4( int[] ary ) { //_arys++; if( ary == null ) return putInt(-1); putInt(ary.length); // Note: based on Brandon commit this should improve performance during parse (7d950d622ee3037555ecbab0e39404f8f0917652) if (ary.length*4 > _bb.remaining()) { expandByteBuffer(ary.length*4); // Try to expand BB buffer to fit input array } int sofar = 0; while( sofar < ary.length ) { IntBuffer ib = _bb.asIntBuffer(); int len = Math.min(ary.length - sofar, ib.remaining()); ib.put(ary, sofar, len); sofar += len; _bb.position(_bb.position() + ib.position()*4); if( sofar < ary.length ) sendPartial(); } return this; } public AutoBuffer putA8( long[] ary ) { //_arys++; if( ary == null ) return putInt(-1); // Trim leading & trailing zeros. Pass along the length of leading & // trailing zero sections, and the non-zero section in the middle. int x=0; for( ; x<ary.length; x++ ) if( ary[x ]!=0 ) break; int y=ary.length; for( ; y>x; y-- ) if( ary[y-1]!=0 ) break; int nzlen = y-x; putInt(x); putInt(nzlen); if( nzlen > 0 ) // If any trailing nulls putInt(ary.length-y); // Trailing zeros // Size trim the NZ section: pass as bytes or shorts if possible. long min=Long.MAX_VALUE, max=Long.MIN_VALUE; for( int i=x; i<y; i++ ) { if( ary[i]<min ) min=ary[i]; if( ary[i]>max ) max=ary[i]; } if( 0 <= min && max < 256 ) { // Ship as unsigned bytes put1(1); for( int i=x; i<y; i++ ) put1((int)ary[i]); return this; } if( Short.MIN_VALUE <= min && max < Short.MAX_VALUE ) { // Ship as shorts put1(2); for( int i=x; i<y; i++ ) put2((short)ary[i]); return this; } if( Integer.MIN_VALUE <= min && max < Integer.MAX_VALUE ) { // Ship as ints put1(4); for( int i=x; i<y; i++ ) put4((int)ary[i]); return this; } put1(8); // Ship as full longs int sofar = x; if ((y-sofar)*8 > _bb.remaining()) expandByteBuffer(ary.length*8); while( sofar < y ) { LongBuffer lb = _bb.asLongBuffer(); int len = Math.min(y - sofar, lb.remaining()); lb.put(ary, sofar, len); sofar += len; _bb.position(_bb.position() + lb.position() * 8); if( sofar < y ) sendPartial(); } return this; } public AutoBuffer putA4f( float[] ary ) { //_arys++; if( ary == null ) return putInt(-1); putInt(ary.length); if (ary.length*4 > _bb.remaining()) expandByteBuffer(ary.length*4); int sofar = 0; while( sofar < ary.length ) { FloatBuffer fb = _bb.asFloatBuffer(); int len = Math.min(ary.length - sofar, fb.remaining()); fb.put(ary, sofar, len); sofar += len; _bb.position(_bb.position() + fb.position()*4); if( sofar < ary.length ) sendPartial(); } return this; } public AutoBuffer putA8d( double[] ary ) { //_arys++; if( ary == null ) return putInt(-1); putInt(ary.length); if (ary.length*8 > _bb.remaining()) expandByteBuffer(ary.length*8); int sofar = 0; while( sofar < ary.length ) { DoubleBuffer db = _bb.asDoubleBuffer(); int len = Math.min(ary.length - sofar, db.remaining()); db.put(ary, sofar, len); sofar += len; _bb.position(_bb.position() + db.position()*8); if( sofar < ary.length ) sendPartial(); } return this; } public AutoBuffer putAA1( byte[][] ary ) { //_arys++; long xy = putZA(ary); if( xy == -1 ) return this; int x=(int)(xy>>32); int y=(int)xy; for( int i=x; i<x+y; i++ ) putA1(ary[i]); return this; } @SuppressWarnings("unused") AutoBuffer putAA2( short[][] ary ) { //_arys++; long xy = putZA(ary); if( xy == -1 ) return this; int x=(int)(xy>>32); int y=(int)xy; for( int i=x; i<x+y; i++ ) putA2(ary[i]); return this; } public AutoBuffer putAA4( int[][] ary ) { //_arys++; long xy = putZA(ary); if( xy == -1 ) return this; int x=(int)(xy>>32); int y=(int)xy; for( int i=x; i<x+y; i++ ) putA4(ary[i]); return this; } @SuppressWarnings("unused") public AutoBuffer putAA4f( float[][] ary ) { //_arys++; long xy = putZA(ary); if( xy == -1 ) return this; int x=(int)(xy>>32); int y=(int)xy; for( int i=x; i<x+y; i++ ) putA4f(ary[i]); return this; } public AutoBuffer putAA8( long[][] ary ) { //_arys++; long xy = putZA(ary); if( xy == -1 ) return this; int x=(int)(xy>>32); int y=(int)xy; for( int i=x; i<x+y; i++ ) putA8(ary[i]); return this; } @SuppressWarnings("unused") public AutoBuffer putAA8d( double[][] ary ) { //_arys++; long xy = putZA(ary); if( xy == -1 ) return this; int x=(int)(xy>>32); int y=(int)xy; for( int i=x; i<x+y; i++ ) putA8d(ary[i]); return this; } public AutoBuffer putAAA4( int[][][] ary ) { //_arys++; long xy = putZA(ary); if( xy == -1 ) return this; int x=(int)(xy>>32); int y=(int)xy; for( int i=x; i<x+y; i++ ) putAA4(ary[i]); return this; } public AutoBuffer putAAA8( long[][][] ary ) { //_arys++; long xy = putZA(ary); if( xy == -1 ) return this; int x=(int)(xy>>32); int y=(int)xy; for( int i=x; i<x+y; i++ ) putAA8(ary[i]); return this; } public AutoBuffer putAAA8d( double[][][] ary ) { //_arys++; long xy = putZA(ary); if( xy == -1 ) return this; int x=(int)(xy>>32); int y=(int)xy; for( int i=x; i<x+y; i++ ) putAA8d(ary[i]); return this; } // Put a String as bytes (not chars!) public AutoBuffer putStr( String s ) { if( s==null ) return putInt(-1); return putA1(StringUtils.bytesOf(s)); } @SuppressWarnings("unused") public AutoBuffer putEnum( Enum x ) { return put1(x==null ? -1 : x.ordinal()); } public static byte[] javaSerializeWritePojo(Object o) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream out = null; try { out = new ObjectOutputStream(bos); out.writeObject(o); out.close(); return bos.toByteArray(); } catch (IOException e) { throw Log.throwErr(e); } } public static Object javaSerializeReadPojo(byte [] bytes) { try { final ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes)); Object o = ois.readObject(); return o; } catch (IOException e) { String className = nameOfClass(bytes); throw Log.throwErr(new RuntimeException("Failed to deserialize " + className, e)); } catch (ClassNotFoundException e) { throw Log.throwErr(e); } } static String nameOfClass(byte[] bytes) { if (bytes == null) return "(null)"; if (bytes.length < 11) return "(no name)"; int nameSize = Math.min(40, Math.max(3, bytes[7])); return new String(bytes, 8, Math.min(nameSize, bytes.length - 8)); } // ========================================================================== // Java Serializable objects // Note: These are heck-a-lot more expensive than their Freezable equivalents. @SuppressWarnings("unused") public AutoBuffer putSer( Object obj ) { if (obj == null) return putA1(null); return putA1(javaSerializeWritePojo(obj)); } @SuppressWarnings("unused") public AutoBuffer putASer(Object[] fs) { //_arys++; long xy = putZA(fs); if( xy == -1 ) return this; int x=(int)(xy>>32); int y=(int)xy; for( int i=x; i<x+y; i++ ) putSer(fs[i]); return this; } @SuppressWarnings("unused") public AutoBuffer putAASer(Object[][] fs) { //_arys++; long xy = putZA(fs); if( xy == -1 ) return this; int x=(int)(xy>>32); int y=(int)xy; for( int i=x; i<x+y; i++ ) putASer(fs[i]); return this; } @SuppressWarnings("unused") public AutoBuffer putAAASer(Object[][][] fs) { //_arys++; long xy = putZA(fs); if( xy == -1 ) return this; int x=(int)(xy>>32); int y=(int)xy; for( int i=x; i<x+y; i++ ) putAASer(fs[i]); return this; } @SuppressWarnings("unused") public Object getSer() { byte[] ba = getA1(); return ba == null ? null : javaSerializeReadPojo(ba); } @SuppressWarnings("unused") public <T> T getSer(Class<T> tc) { return (T)getSer(); } @SuppressWarnings("unused") public <T> T[] getASer(Class<T> tc) { //_arys++; long xy = getZA(); if( xy == -1 ) return null; int x=(int)(xy>>32); // Leading nulls int y=(int)xy; // Middle non-zeros int z = y==0 ? 0 : getInt(); // Trailing nulls T[] ts = (T[]) Array.newInstance(tc, x+y+z); for( int i = x; i < x+y; ++i ) ts[i] = getSer(tc); return ts; } @SuppressWarnings("unused") public <T> T[][] getAASer(Class<T> tc) { //_arys++; long xy = getZA(); if( xy == -1 ) return null; int x=(int)(xy>>32); // Leading nulls int y=(int)xy; // Middle non-zeros int z = y==0 ? 0 : getInt(); // Trailing nulls T[][] ts = (T[][]) Array.newInstance(tc, x+y+z); for( int i = x; i < x+y; ++i ) ts[i] = getASer(tc); return ts; } @SuppressWarnings("unused") public <T> T[][][] getAAASer(Class<T> tc) { //_arys++; long xy = getZA(); if( xy == -1 ) return null; int x=(int)(xy>>32); // Leading nulls int y=(int)xy; // Middle non-zeros int z = y==0 ? 0 : getInt(); // Trailing nulls T[][][] ts = (T[][][]) Array.newInstance(tc, x+y+z); for( int i = x; i < x+y; ++i ) ts[i] = getAASer(tc); return ts; } // ========================================================================== // JSON AutoBuffer printers public AutoBuffer putJNULL( ) { return put1('n').put1('u').put1('l').put1('l'); } // Escaped JSON string private AutoBuffer putJStr( String s ) { byte[] b = StringUtils.bytesOf(s); int off=0; for( int i=0; i<b.length; i++ ) { if( b[i] == '\\' || b[i] == '"') { // Double up backslashes, escape quotes putA1(b,off,i); // Everything so far (no backslashes) put1('\\'); // The extra backslash off=i; // Advance the "so far" variable } // Handle remaining special cases in JSON // if( b[i] == '/' ) { putA1(b,off,i); put1('\\'); put1('/'); off=i+1; continue;} if( b[i] == '\b' ) { putA1(b,off,i); put1('\\'); put1('b'); off=i+1; continue;} if( b[i] == '\f' ) { putA1(b,off,i); put1('\\'); put1('f'); off=i+1; continue;} if( b[i] == '\n' ) { putA1(b,off,i); put1('\\'); put1('n'); off=i+1; continue;} if( b[i] == '\r' ) { putA1(b,off,i); put1('\\'); put1('r'); off=i+1; continue;} if( b[i] == '\t' ) { putA1(b,off,i); put1('\\'); put1('t'); off=i+1; continue;} // ASCII Control characters if( b[i] == 127 ) { putA1(b,off,i); put1('\\'); put1('u'); put1('0'); put1('0'); put1('7'); put1('f'); off=i+1; continue;} if( b[i] >= 0 && b[i] < 32 ) { String hexStr = Integer.toHexString(b[i]); putA1(b, off, i); put1('\\'); put1('u'); for (int j = 0; j < 4 - hexStr.length(); j++) put1('0'); for (int j = 0; j < hexStr.length(); j++) put1(hexStr.charAt(hexStr.length()-j-1)); off=i+1; } } return putA1(b,off,b.length); } public AutoBuffer putJSONStrUnquoted ( String s ) { return s==null ? putJNULL() : putJStr(s); } public AutoBuffer putJSONStrUnquoted ( String name, String s ) { return s==null ? putJSONStr(name).put1(':').putJNULL() : putJSONStr(name).put1(':').putJStr(s); } public AutoBuffer putJSONName( String s ) { return put1('"').putJStr(s).put1('"'); } public AutoBuffer putJSONStr ( String s ) { return s==null ? putJNULL() : putJSONName(s); } public AutoBuffer putJSONAStr(String[] ss) { if( ss == null ) return putJNULL(); put1('['); for( int i=0; i<ss.length; i++ ) { if( i>0 ) put1(','); putJSONStr(ss[i]); } return put1(']'); } private AutoBuffer putJSONAAStr( String[][] sss) { if( sss == null ) return putJNULL(); put1('['); for( int i=0; i<sss.length; i++ ) { if( i>0 ) put1(','); putJSONAStr(sss[i]); } return put1(']'); } @SuppressWarnings("unused") public AutoBuffer putJSONStr (String name, String s ) { return putJSONStr(name).put1(':').putJSONStr(s); } @SuppressWarnings("unused") public AutoBuffer putJSONAStr (String name, String[] ss ) { return putJSONStr(name).put1(':').putJSONAStr(ss); } @SuppressWarnings("unused") public AutoBuffer putJSONAAStr(String name, String[][]sss) { return putJSONStr(name).put1(':').putJSONAAStr(sss); } @SuppressWarnings("unused") public AutoBuffer putJSONSer (String name, Object o ) { return putJSONStr(name).put1(':').putJNULL(); } @SuppressWarnings("unused") public AutoBuffer putJSONASer (String name, Object[] oo ) { return putJSONStr(name).put1(':').putJNULL(); } @SuppressWarnings("unused") public AutoBuffer putJSONAASer (String name, Object[][] ooo ) { return putJSONStr(name).put1(':').putJNULL(); } @SuppressWarnings("unused") public AutoBuffer putJSONAAASer(String name, Object[][][] oooo) { return putJSONStr(name).put1(':').putJNULL(); } public AutoBuffer putJSONAZ( String name, boolean[] f) { return putJSONStr(name).put1(':').putJSONAZ(f); } public AutoBuffer putJSON(Freezable ice) { return ice == null ? putJNULL() : ice.writeJSON(this); } public AutoBuffer putJSONA( Freezable fs[] ) { if( fs == null ) return putJNULL(); put1('['); for( int i=0; i<fs.length; i++ ) { if( i>0 ) put1(','); putJSON(fs[i]); } return put1(']'); } public AutoBuffer putJSONAA( Freezable fs[][]) { if( fs == null ) return putJNULL(); put1('['); for( int i=0; i<fs.length; i++ ) { if( i>0 ) put1(','); putJSONA(fs[i]); } return put1(']'); } public AutoBuffer putJSONAAA( Freezable fs[][][]) { if( fs == null ) return putJNULL(); put1('['); for( int i=0; i<fs.length; i++ ) { if( i>0 ) put1(','); putJSONAA(fs[i]); } return put1(']'); } @SuppressWarnings("unused") public AutoBuffer putJSON ( String name, Freezable f ) { return putJSONStr(name).put1(':').putJSON (f); } public AutoBuffer putJSONA ( String name, Freezable f[] ) { return putJSONStr(name).put1(':').putJSONA (f); } @SuppressWarnings("unused") public AutoBuffer putJSONAA( String name, Freezable f[][]){ return putJSONStr(name).put1(':').putJSONAA(f); } @SuppressWarnings("unused") public AutoBuffer putJSONAAA( String name, Freezable f[][][]){ return putJSONStr(name).put1(':').putJSONAAA(f); } @SuppressWarnings("unused") public AutoBuffer putJSONZ( String name, boolean value ) { return putJSONStr(name).put1(':').putJStr("" + value); } private AutoBuffer putJSONAZ(boolean [] b) { if (b == null) return putJNULL(); put1('['); for( int i = 0; i < b.length; ++i) { if (i > 0) put1(','); putJStr(""+b[i]); } return put1(']'); } // Most simple integers private AutoBuffer putJInt( int i ) { byte b[] = StringUtils.toBytes(i); return putA1(b,b.length); } public AutoBuffer putJSON1( byte b ) { return putJInt(b); } public AutoBuffer putJSONA1( byte ary[] ) { if( ary == null ) return putJNULL(); put1('['); for( int i=0; i<ary.length; i++ ) { if( i>0 ) put1(','); putJSON1(ary[i]); } return put1(']'); } private AutoBuffer putJSONAA1(byte ary[][]) { if( ary == null ) return putJNULL(); put1('['); for( int i=0; i<ary.length; i++ ) { if( i>0 ) put1(','); putJSONA1(ary[i]); } return put1(']'); } @SuppressWarnings("unused") public AutoBuffer putJSON1 (String name, byte b ) { return putJSONStr(name).put1(':').putJSON1(b); } @SuppressWarnings("unused") public AutoBuffer putJSONA1 (String name, byte b[] ) { return putJSONStr(name).put1(':').putJSONA1(b); } @SuppressWarnings("unused") public AutoBuffer putJSONAA1(String name, byte b[][]) { return putJSONStr(name).put1(':').putJSONAA1(b); } public AutoBuffer putJSONAEnum(String name, Enum[] enums) { return putJSONStr(name).put1(':').putJSONAEnum(enums); } public AutoBuffer putJSONAEnum( Enum[] enums ) { if( enums == null ) return putJNULL(); put1('['); for( int i=0; i<enums.length; i++ ) { if( i>0 ) put1(','); putJSONEnum(enums[i]); } return put1(']'); } AutoBuffer putJSON2( char c ) { return putJSON4(c); } AutoBuffer putJSON2( String name, char c ) { return putJSONStr(name).put1(':').putJSON2(c); } AutoBuffer putJSON2( short c ) { return putJSON4(c); } AutoBuffer putJSON2( String name, short c ) { return putJSONStr(name).put1(':').putJSON2(c); } public AutoBuffer putJSONA2( String name, short ary[] ) { return putJSONStr(name).put1(':').putJSONA2(ary); } AutoBuffer putJSONA2( short ary[] ) { if( ary == null ) return putJNULL(); put1('['); for( int i=0; i<ary.length; i++ ) { if( i>0 ) put1(','); putJSON2(ary[i]); } return put1(']'); } AutoBuffer putJSON8 ( long l ) { return putJStr(Long.toString(l)); } AutoBuffer putJSONA8( long ary[] ) { if( ary == null ) return putJNULL(); put1('['); for( int i=0; i<ary.length; i++ ) { if( i>0 ) put1(','); putJSON8(ary[i]); } return put1(']'); } AutoBuffer putJSONAA8( long ary[][] ) { if( ary == null ) return putJNULL(); put1('['); for( int i=0; i<ary.length; i++ ) { if( i>0 ) put1(','); putJSONA8(ary[i]); } return put1(']'); } AutoBuffer putJSONAAA8( long ary[][][] ) { if( ary == null ) return putJNULL(); put1('['); for( int i=0; i<ary.length; i++ ) { if( i>0 ) put1(','); putJSONAA8(ary[i]); } return put1(']'); } AutoBuffer putJSONEnum( Enum e ) { return e==null ? putJNULL() : put1('"').putJStr(e.toString()).put1('"'); } public AutoBuffer putJSON8 ( String name, long l ) { return putJSONStr(name).put1(':').putJSON8(l); } public AutoBuffer putJSONEnum( String name, Enum e ) { return putJSONStr(name).put1(':').putJSONEnum(e); } public AutoBuffer putJSONA8( String name, long ary[] ) { return putJSONStr(name).put1(':').putJSONA8(ary); } public AutoBuffer putJSONAA8( String name, long ary[][] ) { return putJSONStr(name).put1(':').putJSONAA8(ary); } public AutoBuffer putJSONAAA8( String name, long ary[][][] ) { return putJSONStr(name).put1(':').putJSONAAA8(ary); } public AutoBuffer putJSON4(int i) { return putJStr(Integer.toString(i)); } AutoBuffer putJSONA4( int[] a) { if( a == null ) return putJNULL(); put1('['); for( int i=0; i<a.length; i++ ) { if( i>0 ) put1(','); putJSON4(a[i]); } return put1(']'); } AutoBuffer putJSONAA4( int[][] a ) { if( a == null ) return putJNULL(); put1('['); for( int i=0; i<a.length; i++ ) { if( i>0 ) put1(','); putJSONA4(a[i]); } return put1(']'); } AutoBuffer putJSONAAA4( int[][][] a ) { if( a == null ) return putJNULL(); put1('['); for( int i=0; i<a.length; i++ ) { if( i>0 ) put1(','); putJSONAA4(a[i]); } return put1(']'); } public AutoBuffer putJSON4 ( String name, int i ) { return putJSONStr(name).put1(':').putJSON4(i); } public AutoBuffer putJSONA4( String name, int[] a) { return putJSONStr(name).put1(':').putJSONA4(a); } public AutoBuffer putJSONAA4( String name, int[][] a ) { return putJSONStr(name).put1(':').putJSONAA4(a); } public AutoBuffer putJSONAAA4( String name, int[][][] a ) { return putJSONStr(name).put1(':').putJSONAAA4(a); } AutoBuffer putJSON4f ( float f ) { return f==Float.POSITIVE_INFINITY?putJSONStr(JSON_POS_INF):(f==Float.NEGATIVE_INFINITY?putJSONStr(JSON_NEG_INF):(Float.isNaN(f)?putJSONStr(JSON_NAN):putJStr(Float .toString(f)))); } public AutoBuffer putJSON4f ( String name, float f ) { return putJSONStr(name).put1(':').putJSON4f(f); } AutoBuffer putJSONA4f( float[] a ) { if( a == null ) return putJNULL(); put1('['); for( int i=0; i<a.length; i++ ) { if( i>0 ) put1(','); putJSON4f(a[i]); } return put1(']'); } public AutoBuffer putJSONA4f(String name, float[] a) { putJSONStr(name).put1(':'); return putJSONA4f(a); } AutoBuffer putJSONAA4f(String name, float[][] a) { putJSONStr(name).put1(':'); if( a == null ) return putJNULL(); put1('['); for( int i=0; i<a.length; i++ ) { if( i>0 ) put1(','); putJSONA4f(a[i]); } return put1(']'); } AutoBuffer putJSON8d( double d ) { if (TwoDimTable.isEmpty(d)) return putJNULL(); return d==Double.POSITIVE_INFINITY?putJSONStr(JSON_POS_INF):(d==Double.NEGATIVE_INFINITY?putJSONStr(JSON_NEG_INF):(Double.isNaN(d)?putJSONStr(JSON_NAN):putJStr(Double.toString(d)))); } public AutoBuffer putJSON8d( String name, double d ) { return putJSONStr(name).put1(':').putJSON8d(d); } public AutoBuffer putJSONA8d( String name, double[] a ) { return putJSONStr(name).put1(':').putJSONA8d(a); } public AutoBuffer putJSONAA8d( String name, double[][] a) { return putJSONStr(name).put1(':').putJSONAA8d(a); } public AutoBuffer putJSONAAA8d( String name, double[][][] a) { return putJSONStr(name).put1(':').putJSONAAA8d(a); } public AutoBuffer putJSONA8d( double[] a ) { if( a == null ) return putJNULL(); put1('['); for( int i=0; i<a.length; i++ ) { if( i>0 ) put1(','); putJSON8d(a[i]); } return put1(']'); } public AutoBuffer putJSONAA8d( double[][] a ) { if( a == null ) return putJNULL(); put1('['); for( int i=0; i<a.length; i++ ) { if( i>0 ) put1(','); putJSONA8d(a[i]); } return put1(']'); } AutoBuffer putJSONAAA8d( double ary[][][] ) { if( ary == null ) return putJNULL(); put1('['); for( int i=0; i<ary.length; i++ ) { if( i>0 ) put1(','); putJSONAA8d(ary[i]); } return put1(']'); } static final String JSON_NAN = "NaN"; static final String JSON_POS_INF = "Infinity"; static final String JSON_NEG_INF = "-Infinity"; }