com.javanut.pronghorn.pipe.Pipe Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of pronghorn-pipes Show documentation
Show all versions of pronghorn-pipes Show documentation
Ring buffer based queuing utility for applications that require high performance and/or a small
footprint. Well suited for embedded and stream based processing.
package com.javanut.pronghorn.pipe;
import java.io.DataInput;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.javanut.pronghorn.pipe.token.OperatorMask;
import com.javanut.pronghorn.pipe.token.TokenBuilder;
import com.javanut.pronghorn.pipe.token.TypeMask;
import com.javanut.pronghorn.pipe.util.PaddedAtomicLong;
import com.javanut.pronghorn.struct.StructRegistry;
import com.javanut.pronghorn.util.Appendables;
import com.javanut.pronghorn.util.ma.RunningStdDev;
/**
*
* Schema aware data pipe implemented as an internal pair of ring buffers.
*
* One ring holds all the fixed-length fields and the fixed-length meta data relating to the variable-length
* (unstructured fields). The other ring holds only bytes which back the variable-length fields like Strings or Images.
*
* The supported Schema is defined in the FieldReferenceOffsetManager passed in upon construction. The Schema is
* made up of Messages. Messages are made up of one or more fixed-length fragments.
*
* The Message fragments enable direct lookup of fields within sequences and enable the consumption of larger messages
* than would fit within the defined limits of the buffers.
*
* @author Nathan Tippy
*
* //cas: this needs expanded explanation of what a slot is. (best if done above.)
* Storage:
* int - 1 slot
* long - 2 slots, high then low
* text - 2 slots, index then length (if index is negative use constant array)
* decimal - 3 slots, exponent then long mantissa
*
* SlabRing - structured fixed size records
* BlobRing - unstructured variable length field data
*
* StructuredLayoutRing - These have strong type definition per field in addition to being fixed-length.
* UnstructuredLayoutRing - These are just bytes that are commonly UTF-8 encoded strings but may be image data or
* even video/audio streams.
*
* @since 0.1
*
*/
public class Pipe> {
public static int showPipesCreatedLargerThan = -1;
private static final AtomicInteger pipeCounter = new AtomicInteger(0);
//package protected to be called by low and high level API
PipePublishListener[] pubListeners = new PipePublishListener[0];
private StructRegistry typeData;
/**
* Adds a pub listener to the specified pipe
* @param p pipe to be used
* @param listener pub listener to add
*/
public static void addPubListener(Pipe p, PipePublishListener listener) {
p.pubListeners = grow(p.pubListeners, listener);
}
/**
* Removes a pub listener from the specified pipe
* @param p pipe to be used
* @param listener pub listener to remove
*/
public static void removePubListener(Pipe p, PipePublishListener listener) {
p.pubListeners = shrink(p.pubListeners, listener);
}
private static PipePublishListener[] grow(PipePublishListener[] source, PipePublishListener listener) {
PipePublishListener[] result = new PipePublishListener[source.length+1];
System.arraycopy(source, 0, result, 0, source.length);
result[source.length] = listener;
return result;
}
private static PipePublishListener[] shrink(PipePublishListener[] source, PipePublishListener listener) {
int matches = 0;
int i = source.length;
while (--i>=0) {
if (source[i]==listener) {
matches++;
}
}
PipePublishListener[] result = new PipePublishListener[source.length-matches];
i=0;
for(int j = 0; jN.B. A PaddedLong is initialized to zero. There is no problem invoking this method on a PaddedLong
* that has never had the set method called. It may not achieve the desired effect, but it will not cause a
* runtime error.
* @param pl is the padded long containing the value to increment.
* @param inc is the amount to add.
* @return the incremented value of the provided padded long instance.
*/
public static long add(PaddedLong pl, long inc) {
return pl.value += inc;
}
/**
* Provides a readable representation of the value of this padded long instance.
* @return a String of the Long value of this padded long instance.
*/
public String toString() {
return Long.toString(value);
}
}
/**
* Provides a container holding an int value that fills a 64-byte cache line.
*/
public static class PaddedInt {
// Most platforms have 64 byte cache lines so the value variable is padded so 16 four byte ints are consumed.
// If a platform has smaller cache lines, this approach will use a little more memory than required but the
// performance gains will still be preserved.
// Modern Intel and AMD chips commonly have 64 byte cache lines.
// TODO: code This should just be 15, shouldn't it?
public int value = 0, padding1, padding2, padding3, padding4, padding5, padding6, padding7, padding8,
padding9, padding10, padding11, padding13, padding14, padding15, padding16;
/**
* Provides access to the value of this PaddedInt.
* @param pi is the PaddedInt containing the desired value.
* @return the value contained by the provided int.
*/
public static int get(PaddedInt pi) {
return pi.value;
}
/**
* Sets the value of the provided PaddedInt.
* @param pi is the padded int to contain the value.
* @param value is the value to be put into the padded int.
*/
public static void set(PaddedInt pi, int value) {
pi.value = value;
}
/**
* Adds the provided increment to the existing value of the int.
* N.B. A PaddedInt is initialized to zero. There is not problem invoking this method on a PaddedInt
* that has never had the set method called. It may not achieve the desired effect, but it will not cause a
* runtime error.
* @param pi is the padded int containing the value to increment.
* @param inc is the amount to add.
* @return the incremented value of the provided padded int instance.
*/
public static int add(PaddedInt pi, int inc) {
return pi.value += inc;
}
/**
* Provides an increment routine to support the need to wrap the head and tail markers of a buffer from the
* maximum int value to 0 without going negative. The method adds the provided increment to the existing value
* of the provided PaddedInt. The resultant sum is and
ed to the provided mask to remove any
* sign bit that may have been set in the case of an overflow of the maximum-sized integer.
* N.B. A PaddedInt is initialized to zero. There is no problem invoking this method on a PaddedInt
* that has never had the set method called. It may not achieve the desired effect, but it will not cause a
* runtime error.
* @param pi is the padded int containing the value to increment.
* @param inc is the amount to add.
* @return the incremented value of the provided padded int instance.
*/
public static int maskedAdd(PaddedInt pi, int inc, int wrapMask) {
return pi.value = wrapMask & (inc + pi.value);
}
/**
* Provides a readable representation of the value of this padded long instance.
* @return a String of the Long value of this padded long instance.
*/
public String toString() {
return Integer.toString(value);
}
}
private static final Logger log = LoggerFactory.getLogger(Pipe.class);
//I would like to follow the convention where all caps constants are used to indicate static final values which are resolved at compile time.
//This is distinct from other static finals which hold run time instances and values computed from runtime input.
//The reason for this distinction is that these members have special properties.
// A) the literal value replaces the variable by the compiler so.. a change of value requires a recompile of all dependent jars.
// B) these are the only variables which are allowed as case values in switch statements.
//This mask is used to filter the meta value used for variable-length fields.
//after applying this mask to meta the result is always the relative offset within the byte buffer of where the variable-length data starts.
//NOTE: when the 32nd bit is set we will not pull the value from the ring buffer but instead use the constants array (these are pronouns)
//NOTE: when the 31nd bit is set this blob is structured.
public static final int RELATIVE_POS_MASK = 0x3FFFFFFF; //removes flag bits which indicate this is a constant and/or structure
public static final int STRUCTURED_POS_MASK = 0x40000000;
static {
assert(STRUCTURED_POS_MASK == StructRegistry.IS_STRUCT_BIT);
}
//This mask is here to support the fact that variable-length fields will run out of space because the head/tail are 32 bit ints instead of
//longs that are used for the structured layout data. This mask enables the int to wrap back down to zero instead of going negative.
//this will only happen once for every 2GB written.
public static final int BYTES_WRAP_MASK = 0x7FFFFFFF;//NOTE: this trick only works because its 1 bit less than the roll-over sign bit
//A few corner use cases require a poison pill EOF message to be sent down the pipes to ensure each consumer knows when to shut down.
//This is here for compatibility with legacy APIs, This constant is the size of the EOF message.
public static final int EOF_SIZE = 2;
//these public fields are fine because they are all final
public final int id;
public final int sizeOfSlabRing;
public final int sizeOfBlobRing;
public final int slabMask;
public final int blobMask;
public final byte bitsOfSlabRing;
public final byte bitsOfBlogRing;
public final int maxVarLen;//to be used when copying data in dense chunks.
private final T schema;
final boolean usingHighLevelAPI;
private final PipeConfig config;
//TODO: B, need to add constant for gap always kept after head and before tail, this is for debug mode to store old state upon error. NEW FEATURE.
// the time slices of the graph will need to be kept for all rings to reconstruct history later.
private final SlabRingHead slabRingHead = new SlabRingHead();
private final BlobRingHead blobRingHead = new BlobRingHead();
LowLevelAPIWritePositionCache llWrite; //low level write head pos cache and target
LowLevelAPIReadPositionCache llRead; //low level read tail pos cache and target
//TODO: C, add a configuration to disable this construction when we know it s not used.
StackStateWalker ringWalker;//only needed for high level API
//TODO: C, add a configuration to disable this construction when we know it s not used.
PendingReleaseData pendingReleases;//only used when we want to release blob data async from our walking of each fragment
final SlabRingTail slabRingTail = new SlabRingTail(); //primary working and public
private final BlobRingTail blobRingTail = new BlobRingTail(); //primary working and public
//these values are only modified and used when replay is NOT in use
//hold the publish position when batching so the batch can be flushed upon shutdown and thread context switches
private int lastReleasedBlobTail;
long lastReleasedSlabTail;
int blobWriteLastConsumedPos = 0;
private long totalWrittenFragments = 0;
private long lastFragmentCount = 0;
private RunningStdDev fragsPerPass = new RunningStdDev();
//All references found in the messages/fragments to variable-length content are relative. These members hold the current
//base offset to which the relative value is added to find the absolute position in the ring.
//These values are only updated as each fragment is consumed or produced.
private int blobWriteBase = 0;
private int blobReadBase = 0;
//Non Uniform Memory Architectures (NUMA) are supported by the Java Virtual Machine(JVM)
//However there are some limitations.
// A) NUMA support must be enabled with the command line argument
// B) The heap space must be allocated by the same thread which expects to use it long term.
//
// As a result of the above the construction of the buffers is postponed and done with an initBuffers() method.
// The initBuffers() method will be called by the consuming thread before the pipe is used. (see Pronghorn)
public byte[] blobRing; //TODO: B, these two must remain public until the meta/sql modules are fully integrated.
private int[] slabRing;
//defined externally and never changes
protected final byte[] blobConstBuffer;
private byte[][] blobRingLookup;
//NOTE:
// These are provided for seamless integration with the APIs that use ByteBuffers
// This include the SSLEngine and SocketChanels and many other NIO APIs
// For performance reasons ByteBuffers should not be used unless the API has no other integration points.
// Using the Pipes directly is more perfomrmant and the use of DataOutput and DataInput should also be considered.
private IntBuffer wrappedSlabRing;
private ByteBuffer wrappedBlobReadingRingA;
private ByteBuffer wrappedBlobReadingRingB;
private ByteBuffer wrappedBlobWritingRingA;
private ByteBuffer wrappedBlobWritingRingB;
private ByteBuffer[] wrappedWritingBuffers;
private ByteBuffer[] wrappedReadingBuffers;
private ByteBuffer wrappedBlobConstBuffer;
// These are the recommended objects to be used for reading and writing streams into the blob
private DataOutputBlobWriter blobWriter;
private DataInputBlobReader blobReader;
//for writes validates that bytes of var length field is within the expected bounds.
private int varLenMovingAverage = 0;//this is an exponential moving average
static final int JUMP_MASK = 0xFFFFF;
//Exceptions must not occur within consumers/producers of rings however when they do we no longer have
//a clean understanding of state. To resolve the problem all producers and consumers must also shutdown.
//This flag passes the signal so any producer/consumer that sees it on knows to shut down and pass on the flag.
private final AtomicBoolean imperativeShutDown = new AtomicBoolean(false);
private PipeException firstShutdownCaller = null;
//hold the batch positions, when the number reaches zero the records are send or released
private int batchReleaseCountDown = 0;
int batchReleaseCountDownInit = 0;
private int batchPublishCountDown = 0;
private int batchPublishCountDownInit = 0;
//cas: jdoc -- This is the first mention of batch(ing). It would really help the maintainer's comprehension of what
// you mean if you would explain this hugely overloaded word somewhere prior to use -- probably in the class's javadoc.
//hold the publish position when batching so the batch can be flushed upon shutdown and thread context switches
private int lastPublishedBlobRingHead;
private long lastPublishedSlabRingHead;
private final int debugFlags;
private long holdingSlabWorkingTail;
private int holdingBlobWorkingTail;
private int holdingBlobReadBase;
private PipeRegulator regulatorConsumer; //disabled by default
private PipeRegulator regulatorProducer; //disabled by default
//for monitoring only
public int lastMsgIdx; //last msgId read
//helper method for those stages that are not watching for the poison pill.
private long knownPositionOfEOF = Long.MAX_VALUE;
private long markedHeadSlab;
private int markedHeadBlob;
private long markedTailSlab;
private int markedTailBlob;
private int activeBlobHead = -1;
//////////////////
/////////////////
private static ThreadBasedCallerLookup callerLookup;
/**
* Holds the thread caller for internal bookkeeping. This allows the code
* to assert that only a single thread is involved in the calling.
* @param callerLookup object holding the coller id.
*/
public static void setThreadCallerLookup(ThreadBasedCallerLookup callerLookup) {
Pipe.callerLookup = callerLookup;
}
static boolean singleThreadPerPipeWrite(int pipeId) {
if (null != callerLookup) {
int callerId = callerLookup.getCallerId();
if (callerId>=0) {
int expected = callerLookup.getProducerId(pipeId);
if (expected>=0) {
assert(callerId == expected) : "Check your graph construction and stage constructors.\n Pipe "+pipeId+" must only have 1 stage therefore 1 thread writing to it.";
}
}
}
return true;
}
static boolean singleThreadPerPipeRead(int pipeId) {
if (null != callerLookup) {
int callerId = callerLookup.getCallerId();
if (callerId>=0) {
int expected = callerLookup.getConsumerId(pipeId);
if (expected>=0) {
assert(callerId == expected) :
"Check your graph construction and stage constructors.\n Pipe "
+pipeId+" must only have 1 stage therefore 1 thread reading from it. CallerId:"+callerId+" Exepected:"+expected;
}
}
}
return true;
}
/////////////////
/////////////////
/**
* Returns the total count of all fragments written to this pipe since creation.
* @param p pipe with count
* @return count of total fragments written
*/
public static long totalWrittenFragments(Pipe> p) {
return p.totalWrittenFragments;
}
public static RunningStdDev totalWrittenFragmentStdDevperPass(Pipe> p) {
return p.fragsPerPass;
}
/**
* Called to accumulate total fragments written to this pipe
* @param p pipe to accumulate
* @param sum total count to be added for this call
*/
public static void sumWrittenFragments(Pipe> p, long sum) {
p.totalWrittenFragments+=sum;
}
////////////////////
////////////////////
/**
* Build new Pipe instance.
* @param config reusable PipeConfig for creating many identical pipes.
*/
public Pipe(PipeConfig config) {
this(config,true);
}
//private static AtomicLong totalMemory = new AtomicLong();
/**
* Build new Pipe instance. Can save some object construction and memory
* if the high level API is not used.
* @param config reusable PipeConfig for creating many identical pipes.
* @param usingHighLevelAPI boolean used to turn off high level if its not used.
*/
public Pipe(PipeConfig config, boolean usingHighLevelAPI) {
this.config = config;
this.usingHighLevelAPI = usingHighLevelAPI;
byte primaryBits = config.slabBits;
byte byteBits = config.blobBits;
byte[] byteConstants = config.byteConst;
this.schema = config.schema;
assert(holdConstructionLocation());
//totalMemory.addAndGet(config.totalBytesAllocated());
// if (schemaName(this).contains("NetPayloadSchema")) {
// System.out.println("new pipe of size "+config.totalBytesAllocated()+" total so far "+totalMemory.get()+" "+config.toString());
// //new Exception("build here").printStackTrace();
// }
debugFlags = config.debugFlags;
if ((showPipesCreatedLargerThan>0) && (config.totalBytesAllocated() >= showPipesCreatedLargerThan) ) {
if (config.totalBytesAllocated() < (1<<11)) {
new Exception("large pipe "+(config.totalBytesAllocated())+" B").printStackTrace();
} else {
if (config.totalBytesAllocated() < (1<<21)) {
new Exception("large pipe "+(config.totalBytesAllocated()>>10)+" KB").printStackTrace();
} else {
new Exception("large pipe "+(config.totalBytesAllocated()>>20)+" MB").printStackTrace();
}
}
}
//Assign the immutable universal id value for this specific instance
//these values are required to keep track of all ring buffers when graphs are built
this.id = pipeCounter.getAndIncrement();
this.bitsOfSlabRing = primaryBits;
this.bitsOfBlogRing = byteBits;
assert(primaryBits<=30) : "Must be 1G or smaller, requested "+byteBits+" bits";
assert(byteBits<=30) : "Must be 1G or smaller, requested "+byteBits+" bits";
assert (primaryBits >= 0); //zero is a special case for a mock ring
//cas: naming. This should be consistent with the maxByteSize, i.e., maxFixedSize or whatever.
//single buffer size for every nested set of groups, must be set to support the largest need.
this.sizeOfSlabRing = 1 << primaryBits;
this.slabMask = Math.max(1, sizeOfSlabRing - 1); //mask can no be any smaller than 1
//single text and byte buffers because this is where the variable-length data will go.
this.sizeOfBlobRing = 1 << byteBits;
this.blobMask = Math.max(1, sizeOfBlobRing - 1); //mask can no be any smaller than 1
FieldReferenceOffsetManager from = MessageSchema.from(config.schema);
this.blobConstBuffer = byteConstants;
if (null==from || 0 == from.maxVarFieldPerUnit || 0==primaryBits) { //zero bits is for the dummy mock case
maxVarLen = 0; //no fragments had any variable-length fields so we never allow any
} else {
//given outer ring buffer this is the maximum number of var fields that can exist at the same time.
int maxVarCount = FieldReferenceOffsetManager.maxVarLenFieldsPerPrimaryRingSize(from, sizeOfSlabRing);
//to allow more almost 2x more flexibility in variable-length bytes we track pairs of writes and ensure the
//two together are below the threshold rather than each alone
maxVarLen = blobMask/maxVarCount;
}
}
private Exception createdStack;
private boolean holdConstructionLocation() {
createdStack = new Exception(config+" Pipe created "+config.totalBytesAllocated()+" bytes");
return true;
}
/**
* Prints the call stack as it was when this pipe was created.
* This is helpful for debugging as to where this pipe was created.
*/
public void creationStack() {
if (null!=createdStack) {
createdStack.printStackTrace();
}
}
private AtomicBoolean isInBlobFieldWrite = new AtomicBoolean(false);
//this is used along with tail position to capture the byte count
private long totalBlobBytesRead=0;
/**
* Bookkeeping method to track if a write is in progress to the blob.
* @param schema
* @param pipe writing
* @return boolean true if the blob is in the process of a write.
*/
public static > boolean isInBlobFieldWrite(Pipe pipe) {
return pipe.isInBlobFieldWrite.get();
}
/**
* total bytes consumed by this fragment
* @return count of bytes consumed
*/
public long totalBlobBytesRead() {
return totalBlobBytesRead;
}
/**
* Prep this pipe for writing a blob var len field.
* Set the internal state for checking if the blob write is in progress.
*/
public void openBlobFieldWrite() {
//System.out.println("open stream on "+id);
if (!isInBlobFieldWrite.compareAndSet(false, true)) {
if (null!=blobOpenStack) {
blobOpenStack.printStackTrace();
}
throw new UnsupportedOperationException("only one open write against the blob at a time.");
}
assert(recordOpenStack());
}
private Exception blobOpenStack;
private boolean recordOpenStack() {
blobOpenStack = new Exception("Blob first opened here but it is attempted to be opened again later.");
return true;
}
/**
* Mark this pipe as finished writing blob var len field.
*/
public void closeBlobFieldWrite() {
blobOpenStack = null;
//System.out.println("close stream on "+id);
if (!isInBlobFieldWrite.compareAndSet(true, false)) {
throw new UnsupportedOperationException("can not close blob if not open.");
}
}
/**
* Check if the rate is limited from the consumer side of the pipe.
* @param schema
* @param pipe which is rate limited
* @return boolean true if consumer side is rate limited
*/
public static > boolean isRateLimitedConsumer(Pipe pipe) {
return null!=pipe.regulatorConsumer;
}
/**
* Check if the rate is limited from the producer side of the pipe.
* @param schema
* @param pipe which is rate limited
* @return boolean true if producer side is rate limited
*/
public static > boolean isRateLimitedProducer(Pipe pipe) {
return null!=pipe.regulatorProducer;
}
/**
* Returns nano-second count of how much time should pass before consuming more data
* from this pipe. This is based on the rate configuration.
*/
public static > long computeRateLimitConsumerDelay(Pipe pipe) {
return PipeRegulator.computeRateLimitDelay(pipe, Pipe.getWorkingTailPosition(pipe), pipe.regulatorConsumer);
}
/**
* Returns nano-second count of how much time should pass before producing more data
* into this pipe. This is based on the rate configuration.
*/
public static > long computeRateLimitProducerDelay(Pipe pipe) {
return PipeRegulator.computeRateLimitDelay(pipe, Pipe.workingHeadPosition(pipe), pipe.regulatorProducer);
}
/**
* Checks if schema matches that used by the pipe.
* @param pipe to check schema
* @param schema MessageSchema instance type
* @return boolean true if the schema matches
*/
public static , T extends MessageSchema> boolean isForSchema(Pipe pipe, T schema) {
return pipe.schema == schema;
}
/**
* Checks if schema matches that used by the pipe.
* @param pipe to check schema
* @param schema MessageSchema class
* @return boolean true if the schema matches
*/
public static , T extends MessageSchema> boolean isForSchema(Pipe pipe, Class schema) {
return schema.isInstance(pipe.schema);
}
/**
* Checks if both pipes use the same schemas.
* @param pipeA pipe with a schema S
* @param pipeB pipe with a schema T
* @return boolean true if the schema matches
*/
public static , T extends MessageSchema> boolean isForSameSchema(Pipe pipeA, Pipe pipeB) {
return pipeA.schema == pipeB.schema;
}
/**
* Checks if pipe uses a dynamic schema which is not backed by an XML contract file.
* @param pipe to be checked
* @return boolean true if the schema is dynamic
*/
public static > boolean isForDynamicSchema(Pipe pipe) {
return pipe.schema instanceof MessageSchemaDynamic;
}
/**
* Estimate of the total bytes consumed by this pipe
* @param pipe to be checked
* @return long bytes count estimate memory consumed
*/
public static > long estBytesAllocated(Pipe pipe) {
if (null!=pipe && pipe.blobRing!=null && pipe.slabRing!=null) {
//System.out.println("slab: "+pipe.slabRing.length+" blob: "+pipe.blobRing.length);
return ((long)pipe.blobRing.length) + (pipe.slabRing.length*4L) + 1024L;//1K for overhead
} else {
return 0;
}
}
/**
* get name of the schema.
* @param pipe to be checked
* @return String name of the schema
*/
public static > String schemaName(Pipe pipe) {
if (null==pipe.cachedSchemaName) {
pipe.cachedSchemaName = (null==pipe.schema ?
"NoSchemaFor "+Pipe.from(pipe).name :
pipe.schema.getClass().getSimpleName()).intern();
}
return pipe.cachedSchemaName;
}
private String cachedSchemaName;
/**
* Back up the cursor read position. So the fragments can be read again for those not yet released.
*
* @param pipe with unreleased fragments to be replayed.
*/
public static > void replayUnReleased(Pipe pipe) {
//We must enforce this but we have a few unit tests that are in violation which need to be fixed first
// if (!RingBuffer.from(ringBuffer).hasSimpleMessagesOnly) {
// throw new UnsupportedOperationException("replay of unreleased messages is not supported unless every message is also a single fragment.");
// }
if (!isReplaying(pipe)) {
//save all working values only once if we re-enter replaying multiple times.
pipe.holdingSlabWorkingTail = Pipe.getWorkingTailPosition(pipe);
pipe.holdingBlobWorkingTail = Pipe.getWorkingBlobTailPosition(pipe);
//NOTE: we must never adjust the ringWalker.nextWorkingHead because this is replay and must not modify write position!
pipe.ringWalker.holdingNextWorkingTail = pipe.ringWalker.nextWorkingTail;
pipe.holdingBlobReadBase = pipe.blobReadBase;
}
//clears the stack and cursor position back to -1 so we assume that the next read will begin a new message
StackStateWalker.resetCursorState(pipe.ringWalker);
//set new position values for high and low api
pipe.ringWalker.nextWorkingTail = pipe.slabRingTail.rollBackWorking();
pipe.blobReadBase = pipe.blobRingTail.rollBackWorking(); //this byte position is used by both high and low api
}
/**
* Checks to see if the provided pipe is replaying.
* @param pipe the ringBuffer to check.
* @return true
if the ringBuffer is replaying, false
if it is not.
*/
public static > boolean isReplaying(Pipe pipe) {
return Pipe.getWorkingTailPosition(pipe)> void cancelReplay(Pipe pipe) {
pipe.slabRingTail.workingTailPos.value = pipe.holdingSlabWorkingTail;
pipe.blobRingTail.byteWorkingTailPos.value = pipe.holdingBlobWorkingTail;
pipe.blobReadBase = pipe.holdingBlobReadBase;
pipe.ringWalker.nextWorkingTail = pipe.ringWalker.holdingNextWorkingTail;
//NOTE while replay is in effect the head can be moved by the other (writing) thread.
}
private static final int INDEX_BASE_OFFSET = 4; //Room for 1 int (struct info)
///////////
//support for adding indexes onto the end of the var len blob field
///////////
/**
* Base byte position where the index begins.
* @param pipe where the index is written.
* @return int position of index
*/
public static > int blobIndexBasePosition(Pipe pipe) {
if (pipe.maxVarLen> void batchAllReleases(Pipe pipe) {
pipe.batchReleaseCountDownInit = Integer.MAX_VALUE;
pipe.batchReleaseCountDown = Integer.MAX_VALUE;
}
/**
* sets specific batch release size, releases do not occur when called for but only when the
* size count of releases is reached.
* @param pipe to have releases batched
* @param size count of fragments to batch before release.
*/
public static > void setReleaseBatchSize(Pipe pipe, int size) {
validateBatchSize(pipe, size);
pipe.batchReleaseCountDownInit = size;
pipe.batchReleaseCountDown = size;
}
public static > void batchFollowingPublishes(Pipe pipe, int count) {
pipe.batchPublishCountDown += count;
}
/**
* sets specific batch publish size, publishes do not occur when called for but only when the
* size count of publishes is reached.
* @param pipe to have publishes batched
* @param size count of fragments to batch befoer publish
*/
public static > void setPublishBatchSize(Pipe pipe, int size) {
validateBatchSize(pipe, size);
pipe.batchPublishCountDownInit = size;
pipe.batchPublishCountDown = size;
}
/**
* get the publish batch count
* @param pipe with batched published count
* @return count of fragments to be batched
*/
public static > int getPublishBatchSize(Pipe pipe) {
return pipe.batchPublishCountDownInit;
}
/**
* get the release batch count
* @param pipe with batched release count
* @return count of fragments to be batched
*/
public static > int getReleaseBatchSize(Pipe pipe) {
return pipe.batchReleaseCountDownInit;
}
/**
* Sets the publish batch size to the max supported by the pipe.
* @param pipe to have publishes batched.
*/
public static > void setMaxPublishBatchSize(Pipe pipe) {
int size = computeMaxBatchSize(pipe, 3);
pipe.batchPublishCountDownInit = size;
pipe.batchPublishCountDown = size;
}
/**
* Sets the release batch size to the max supported by the pipe.
* @param pipe
*/
public static > void setMaxReleaseBatchSize(Pipe pipe) {
int size = computeMaxBatchSize(pipe, 3);
pipe.batchReleaseCountDownInit = size;
pipe.batchReleaseCountDown = size;
}
/**
* base position to track where the var length
* @param pipe holding bytes written
* @return write base
*/
public static > int bytesWriteBase(Pipe pipe) {
return pipe.blobWriteBase;
}
/**
* Store new var len base written position.
* @param pipe to have base marked
*/
public static > void markBytesWriteBase(Pipe pipe) {
pipe.blobWriteBase = pipe.blobRingHead.byteWorkingHeadPos.value;
}
/**
* get base position based on the var length data written.
* @param pipe to read base from
* @return int base position
*/
public static > int bytesReadBase(Pipe pipe) {
assert(validateInsideData(pipe, pipe.blobReadBase));
return pipe.blobReadBase;
}
private static > boolean validateInsideData(Pipe pipe, int value) {
int mHead = Pipe.blobMask(pipe) & Pipe.getBlobHeadPosition(pipe);
int mTail = Pipe.blobMask(pipe) & Pipe.getBlobTailPosition(pipe);
int mValue = Pipe.blobMask(pipe) & value;
if (mTail<=mHead) {
assert(mTail<=mValue && mValue<=mHead) : "tail "+mTail+" readBase "+mValue+" head "+mHead;
return mTail<=mValue && mValue<=mHead;
} else {
assert(mValue<=mHead || mValue>=mTail) : "tail "+mTail+" readBase "+mValue+" head "+mHead;
return mValue<=mHead || mValue>=mTail;
}
}
/**
* store bytes consumed for fragment bookkeeping
* @param pipe reading from
* @param bytesConsumed int total for this read
*/
public static > void markBytesReadBase(Pipe pipe, int bytesConsumed) {
assert(bytesConsumed>=0) : "Bytes consumed must be positive";
//base has future pos added to it so this value must be masked and kept as small as possible
pipe.totalBlobBytesRead = pipe.totalBlobBytesRead+bytesConsumed;
pipe.blobReadBase = pipe.blobMask /*Pipe.BYTES_WRAP_MASK*/ & (pipe.blobReadBase+bytesConsumed);
//no longer true now that we have index data...
//assert(validateInsideData(pipe, pipe.blobReadBase)) : "consumed "+bytesConsumed+" bytes using mask "+pipe.blobMask+" new base is "+pipe.blobReadBase;
}
/**
* auto compute and store bytes consumed for fragment bookkeeping
* @param pipe reading from
*/
public static > void markBytesReadBase(Pipe pipe) {
final int newBasePosition = pipe.blobMask & PaddedInt.get(pipe.blobRingTail.byteWorkingTailPos);
//do comparison to find the new consumed value?
int bytesConsumed = newBasePosition - pipe.blobReadBase;
if (bytesConsumed<0) {
bytesConsumed += pipe.sizeOfBlobRing;
}
pipe.totalBlobBytesRead = pipe.totalBlobBytesRead+bytesConsumed;
//base has future pos added to it so this value must be masked and kept as small as possible
pipe.blobReadBase = newBasePosition;
}
/**
* Helpful user readable summary of the pipe.
* Shows where the head and tail positions are along with how full the ring is at the time of call.
*/
public String toString() {
int contentRem = Pipe.contentRemaining(this);
if (contentRem > sizeOfSlabRing) {
log.warn("ERROR: can not have more slab content than the size of the pipe. content {} vs {}",contentRem,sizeOfSlabRing);
}
StringBuilder result = new StringBuilder();
result.append("RingId<").append(schemaName(this));
Appendables.appendValue(result.append(">:"), id);
Appendables.appendValue(result.append(" slabTailPos "),slabRingTail.tailPos.get());
Appendables.appendValue(result.append(" slabWrkTailPos "),slabRingTail.workingTailPos.value);
Appendables.appendValue(result.append(" slabHeadPos "),slabRingHead.headPos.get());
Appendables.appendValue(result.append(" slabWrkHeadPos "),slabRingHead.workingHeadPos.value);
Appendables.appendValue(result.append(" ").append(contentRem).append("/"),sizeOfSlabRing);
Appendables.appendValue(result.append(" blobTailPos "),PaddedInt.get(blobRingTail.bytesTailPos));
Appendables.appendValue(result.append(" blobWrkTailPos "),blobRingTail.byteWorkingTailPos.value);
Appendables.appendValue(result.append(" blobHeadPos "),PaddedInt.get(blobRingHead.bytesHeadPos));
Appendables.appendValue(result.append(" blobWrkHeadPos "),blobRingHead.byteWorkingHeadPos.value);
Appendables.appendValue(result.append(" blobMask "),blobMask);
if (isEndOfPipe(this, slabRingTail.tailPos.get())) {
Appendables.appendValue(result.append(" Ended at "),this.knownPositionOfEOF);
}
return result.toString();
}
/**
* Return the configuration used for this pipe, Helpful when we need to make clones of the ring which will hold same message types.
*/
public PipeConfig config() {
return config;
}
/**
* Total count of all pipes in the system. As each pipe is created this counter is incremented.
* @return total count of pipes in the application.
*/
public static int totalPipes() {
return pipeCounter.get();
}
/**
* Allocates all internal arrays and buffers before the Pipe can be used
* @return Pipe initialized
*/
public Pipe initBuffers() {
assert(!isInit(this)) : "RingBuffer was already initialized";
if (!isInit(this)) {
buildBuffers();
} else {
log.warn("Init was already called once already on this ring buffer");
}
return this;
}
/**
* Set the regulator value for the consumer of the pipe.
* @param pipe to be regulated
* @param msgPerMs messages per ms
* @param msgSize size of messages regulated
*/
public static > void setConsumerRegulation(Pipe pipe, int msgPerMs, int msgSize) {
assert(null==pipe.regulatorConsumer) : "regulator must only be set once";
assert(!isInit(pipe)) : "regular may only be set before scheduler has initialized the pipe";
pipe.regulatorConsumer = new PipeRegulator(msgPerMs, msgSize);
}
/**
* Set the regulator value for the producer of the pipe.
* @param pipe to be regulated
* @param msgPerMs messages per ms
* @param msgSize size of messages regulated
*/
public static > void setProducerRegulation(Pipe pipe, int msgPerMs, int msgSize) {
assert(null==pipe.regulatorProducer) : "regulator must only be set once";
assert(!isInit(pipe)) : "regular may only be set before scheduler has initialized the pipe";
pipe.regulatorProducer = new PipeRegulator(msgPerMs, msgSize);
}
//private final static AtomicLong totalBytes = new AtomicLong();
private void buildBuffers() {
try {
this.blobRing = new byte[sizeOfBlobRing];
this.slabRing = new int[sizeOfSlabRing];
this.pendingReleases =
((null == schema.from) ? null : //pending releases only supported with real FROM schemas
new PendingReleaseData(
sizeOfSlabRing / FieldReferenceOffsetManager.minFragmentSize(MessageSchema.from(schema))
));
//NOTE: this is only needed for high level API, if only low level is in use it would be nice to not create this
if (usingHighLevelAPI && null!=schema.from) {
this.ringWalker = new StackStateWalker(MessageSchema.from(schema), sizeOfSlabRing);
}
assert(slabRingHead.workingHeadPos.value == slabRingHead.headPos.get());
assert(slabRingTail.workingTailPos.value == slabRingTail.tailPos.get());
assert(slabRingHead.workingHeadPos.value == slabRingTail.workingTailPos.value);
assert(slabRingTail.tailPos.get()==slabRingHead.headPos.get());
long toPos = slabRingHead.workingHeadPos.value;//can use this now that we have confirmed they all match.
this.llRead = new LowLevelAPIReadPositionCache();
this.llWrite = new LowLevelAPIWritePositionCache();
//This init must be the same as what is done in reset()
//This target is a counter that marks if there is room to write more data into the ring without overwriting other data.
llWrite.llwHeadPosCache = toPos;
llRead.llrTailPosCache = toPos;
llRead.llwConfirmedPosition = toPos - sizeOfSlabRing;// TODO: hack test, mask;//must be mask to ensure zero case works.
llWrite.llrConfirmedPosition = toPos;
this.blobRingLookup = new byte[][] {blobRing,blobConstBuffer};
//This assignment is critical to knowing that init was called
this.wrappedSlabRing = IntBuffer.wrap(this.slabRing);
//only create if there is a possibility that they may be used.
if (sizeOfBlobRing>0) {
this.wrappedBlobReadingRingA = ByteBuffer.wrap(this.blobRing);
this.wrappedBlobReadingRingB = ByteBuffer.wrap(this.blobRing);
this.wrappedBlobWritingRingA = ByteBuffer.wrap(this.blobRing);
this.wrappedBlobWritingRingB = ByteBuffer.wrap(this.blobRing);
this.wrappedBlobConstBuffer = null==this.blobConstBuffer?null:ByteBuffer.wrap(this.blobConstBuffer);
this.wrappedReadingBuffers = new ByteBuffer[]{wrappedBlobReadingRingA,wrappedBlobReadingRingB};
this.wrappedWritingBuffers = new ByteBuffer[]{wrappedBlobWritingRingA,wrappedBlobWritingRingB};
assert(0==wrappedBlobReadingRingA.position() && wrappedBlobReadingRingA.capacity()==wrappedBlobReadingRingA.limit()) : "The ByteBuffer is not clear.";
//blobReader and writer must be last since they will be checking isInit in construction.
this.blobReader = createNewBlobReader();
this.blobWriter = createNewBlobWriter();
}
} catch (OutOfMemoryError oome) {
log.warn("attempted to allocate Slab:{} Blob:{} in {}", sizeOfSlabRing, sizeOfBlobRing, this, oome);
shutdown(this);
System.exit(-1);
}
}
//Can be overridden to support specific classes which extend DataInputBlobReader
protected DataInputBlobReader createNewBlobReader() {
return new DataInputBlobReader(this);
}
//Can be overridden to support specific classes which extend DataOutputBlobWriter
protected DataOutputBlobWriter createNewBlobWriter() {
return new DataOutputBlobWriter(this);
}
/**
* is the pipe initialized and ready for use.
* @param pipe
* @return boolean true if initialized
*/
public static > boolean isInit(Pipe pipe) {
//Due to the fact that no locks are used it becomes necessary to check
//every single field to ensure the full initialization of the object
//this is done as part of graph set up and as such is called rarely.
return null!=pipe.blobRing &&
null!=pipe.slabRing &&
null!=pipe.blobRingLookup &&
null!=pipe.wrappedSlabRing &&
null!=pipe.llRead &&
null!=pipe.llWrite &&
(
pipe.sizeOfBlobRing == 0 || //no init of these if the blob is not used
(null!=pipe.wrappedBlobReadingRingA &&
null!=pipe.wrappedBlobReadingRingB &&
null!=pipe.wrappedBlobWritingRingA &&
null!=pipe.wrappedBlobWritingRingB
)
);
//blobReader and blobWriter and not checked since they call isInit on construction
}
/**
* Confirm that the bytes written are less than the space available.
* @param pipe to be checked.
* @param length written in bytes
* @return boolean true if the bytes written are less than the available length.
*/
public static > boolean validateVarLength(Pipe pipe, int length) {
assert(length>=-1) : "invalid length value "+length;
int newAvg = (length+pipe.varLenMovingAverage)>>1;
if (newAvg>pipe.maxVarLen) {
//compute some helpful information to add to the exception
int bytesPerInt = (int)Math.ceil(length*Pipe.from(pipe).maxVarFieldPerUnit);
int bitsDif = 32 - Integer.numberOfLeadingZeros(bytesPerInt - 1);
Pipe.shutdown(pipe);
pipe.creationStack();
throw new UnsupportedOperationException("Can not write byte array of length "+length+
". The dif between slab and byte blob should be at least "+bitsDif+
". "+pipe.bitsOfSlabRing+","+pipe.bitsOfBlogRing+
". The limit is "+pipe.maxVarLen+" for pipe "+pipe);
}
pipe.varLenMovingAverage = newAvg;
return true;
}
/**
* Empty and restore to original values.
*/
public void reset() {
reset(0,0);
}
/**
* Rest to desired position, helpful in unit testing to force wrap off the end.
* @param structuredPos
*/
public void reset(int structuredPos, int unstructuredPos) {
slabRingHead.workingHeadPos.value = structuredPos;
slabRingTail.workingTailPos.value = structuredPos;
slabRingTail.tailPos.set(structuredPos);
slabRingHead.headPos.set(structuredPos);
assert(Pipe.contentRemaining(this)<=sizeOfSlabRing);
if (null!=llWrite) {
llWrite.llwHeadPosCache = structuredPos;
llRead.llrTailPosCache = structuredPos;
llRead.llwConfirmedPosition = structuredPos - sizeOfSlabRing;//mask;sss TODO: hack test.
llWrite.llrConfirmedPosition = structuredPos;
}
blobRingHead.byteWorkingHeadPos.value = unstructuredPos;
PaddedInt.set(blobRingHead.bytesHeadPos,unstructuredPos);
blobWriteBase = unstructuredPos;
blobReadBase = unstructuredPos;
blobWriteLastConsumedPos = unstructuredPos;
blobRingTail.byteWorkingTailPos.value = unstructuredPos;
PaddedInt.set(blobRingTail.bytesTailPos,unstructuredPos);
StackStateWalker.reset(ringWalker, structuredPos);
}
/**
* Publish all the batched up publish calls.
* @param pipe to have publications released.
*/
public static void releaseReadsBatched(Pipe pipe) {
Pipe.batchedReleasePublish(pipe, Pipe.getWorkingBlobTailPosition(pipe),
Pipe.getWorkingTailPosition(pipe));
}
/**
* Returns the slab size of this copied fragment.
* This method assumes there is both a fragment to be copied and
* that there will be room at the destination.
*/
public static > int copyFragment(
Pipe source,
Pipe target) {
return copyFragment(source,
Pipe.tailPosition(source),
Pipe.getBlobTailPosition(source),
target);
}
static > int copyFragment(
Pipe sourcePipe, final long sourceSlabPos, int sourceBlobPos,
Pipe localTarget) {
final int mask = Pipe.slabMask(sourcePipe);
final int[] slab = Pipe.slab(sourcePipe);
final int msgIdx = slab[mask&(int)sourceSlabPos];
//look up the data size to copy...
final int slabMsgSize = msgIdx!=-1 ? Pipe.from(sourcePipe).fragDataSize[msgIdx] : Pipe.EOF_SIZE;
//this value also contains the full byte count for any index used by structures
int blobMsgSize = slab[mask&((int)(sourceSlabPos+slabMsgSize-1))]; //min one for pos of byte count
Pipe.copyFragment(localTarget,
slabMsgSize, blobMsgSize,
Pipe.blob(sourcePipe), Pipe.slab(sourcePipe),
Pipe.blobMask(sourcePipe), Pipe.slabMask(sourcePipe),
sourceBlobPos, (int)sourceSlabPos);
//move source pointers forward
Pipe.addAndGetWorkingTail(sourcePipe, slabMsgSize-1);
Pipe.addAndGetBlobWorkingTailPosition(sourcePipe, blobMsgSize);
Pipe.confirmLowLevelRead(sourcePipe, slabMsgSize);
Pipe.releaseReadLock(sourcePipe);
return slabMsgSize;
}
/**
* Build an array of Pipe instances from a set of PipeConfig.
* @param configs array of PipeConfig values
* @return array of new pipes
*/
public static Pipe[] buildPipes(PipeConfig[] configs) {
int i = configs.length;
Pipe[] result = new Pipe[i];
while (--i>=0) {
result[i] = new Pipe(configs[i]);
}
return result;
}
/**
* Build an array of Pipe instances from a set of PipeConfig found in array of pipes.
* @param Pipe array of pipes
* @param schema
* @return array of new pipes
*/
public static > Pipe[] buildPipes(Pipe[] pipes) {
int i = pipes.length;
Pipe[] result = new Pipe[i];
while (--i>=0) {
result[i] = new Pipe(pipes[i].config);
}
return result;
}
/**
* Build and array of Pipe instances from a single PipeConfig
* @param count of new Pipe instances created
* @param commonConfig PipeConfig used for all the new pipes
* @return array of new pipes
*/
public static > Pipe[] buildPipes(int count, PipeConfig commonConfig) {
Pipe[] result = new Pipe[count];
int i = count;
while (--i >= 0) {
result[i] = new Pipe(commonConfig);
}
return (Pipe[])result;
}
/**
* Checks blob to see if there is data to read
* @param pipe pipe to check
* @param blobPos position of blob
* @param length length of blob
*/
public static > boolean validatePipeBlobHasDataToRead(Pipe pipe, int blobPos, int length) {
assert(length>=0) : "bad length:"+length;
if (length==0) {
return true;//nothing to check in this case.
}
assert(length<=pipe.sizeOfBlobRing) : "length is larger than backing array "+length+" vs "+pipe.sizeOfBlobRing;
//we know that we are looking for a non zero length
assert(Pipe.getBlobHeadPosition(pipe)!=Pipe.getBlobTailPosition(pipe)) : "Needs "+length+" but pipe is empty and can not have any data: "+pipe;
int mHead = Pipe.blobMask(pipe) & Pipe.getBlobHeadPosition(pipe);
int mTail = Pipe.blobMask(pipe) & Pipe.getBlobTailPosition(pipe);
//ensure that starting at testPos up to testLen is all contained between tail and head
int mStart = Pipe.blobMask(pipe) & blobPos;
int stop = mStart+length;
int mStop = Pipe.blobMask(pipe) & stop;
//we have 4 cases where position and length can be inside (inbetween tail and head)
//these cases are all drawn below pllll is the content starting at position p and
//running with p for the full length
//Head - where new data can be written (can only write up to tail)
//Tail - where data is consumed (can only consume up to head)
if ((mStop= pipe.sizeOfBlobRing)) {
////////////////////////////////////////////////////////////
//pppppppppppp H T ppppppppp//
////////////////////////////////////////////////////////////
assert(mStop<=mHead) : "tail "+mTail+" start "+mStart+" stop "+mStop+" head "+mHead+" mask "+pipe.blobMask+" pipe "+pipe;
assert(mTail<=mStart) : "tail "+mTail+" start "+mStart+" stop "+mStop+" head "+mHead+" mask "+pipe.blobMask+" pipe "+pipe;
assert(mHead<=mStart) : "tail "+mTail+" start "+mStart+" stop "+mStop+" head "+mHead+" mask "+pipe.blobMask+" pipe "+pipe;
assert(mHeadmTail) {
////////////////////////////////////////////////////////////
// T pppppppppppppppppppppp H //
////////////////////////////////////////////////////////////
assert(mTail=mTail) {
////////////////////////////////////////////////////////////
// H T pppppppp //
////////////////////////////////////////////////////////////
assert(mHead MessageSchema to extend
* @return addLongAsUTF8(output, value)
*/
public static > int addIntAsASCII(Pipe output, int value) {
validateVarLength(output, 12);
return addLongAsUTF8(output, value);
}
/**
* Write ascii text for rational value
* @param pipe where ascii is written
* @param numerator of rational to write
* @param denominator of rational to write
* @return count of bytes written.
*/
public static > int addRationalAsASCII(Pipe pipe, long numerator, long denominator) {
validateVarLength(pipe, 21);
DataOutputBlobWriter outputStream = Pipe.outputStream(pipe);
outputStream.openField();
Appendables.appendValue(outputStream, numerator);
outputStream.writeChar('/');
Appendables.appendValue(outputStream, denominator);
return outputStream.closeLowLevelField();
}
/**
* Writes in a long as an ASCII character to specified pipe
* @param output pipe to write to
* @param value long to write as ASCII
* @param MessageSchema to extend
* @return addLongAsUTF8(output, value)
*/
public static > int addLongAsASCII(Pipe output, long value) {
return addLongAsUTF8(output, value);
}
/**
* Writes long as UTF8 with specified length to named Pipe
* @param digitBuffer Pipe reference
* @param length length of long to add
* @param MessageSchema to extend
* @return outputStream of Pipe
*/
public static > int addLongAsUTF8(Pipe digitBuffer, long length) {
validateVarLength(digitBuffer, 21);
DataOutputBlobWriter outputStream = Pipe.outputStream(digitBuffer);
outputStream.openField();
Appendables.appendValue(outputStream, length);
return outputStream.closeLowLevelField();
}
/**
* Writes long as UTF8 with specified length to Pipe
* @param digitBuffer Pipe reference
* @param length length of int to add
* @return outputStream of Pipe
*/
public static > int addLongAsUTF8(Pipe digitBuffer, int length) {
validateVarLength(digitBuffer, 21);
DataOutputBlobWriter outputStream = Pipe.outputStream(digitBuffer);
outputStream.openField();
Appendables.appendValue(outputStream, length);
return outputStream.closeLowLevelField();
}
/**
* Evenly split array of pipes across pipeCount arrays of arrays of pipes.
* @param pipeCount int target count of groups
* @param pipes array of pipes to be split
* @return array of array of pipes
*/
public static > Pipe[][] splitPipes(int pipeCount, Pipe[] pipes) {
Pipe[][] result = new Pipe[pipeCount][];
if (pipeCount>=2) {
int fullLen = pipes.length;
int last = 0;
for(int p = 1;p[] newPipe = new Pipe[plen];
System.arraycopy(pipes, last, newPipe, 0, plen);
result[p-1]=newPipe;
last = nextLimit;
}
int plen = fullLen-last;
Pipe[] newPipe = new Pipe[plen];
System.arraycopy(pipes, last, newPipe, 0, plen);
result[pipeCount-1]=newPipe;
} else {
result[0] = pipes;
}
return result;
}
/**
* matching the above splitPipes logic this method produces an inverse lookup array to determine group given a single index.
*
* @param groups
* @param fullLen
* @return array to look up which group a value is in
*/
public static int[] splitGroups(final int groups, final int fullLen) {
int c = 0;
int[] result = new int[fullLen];
int last = 0;
for(int p = 1;p=0) {
result[c++] = p-1;
}
last = nextLimit;
}
int plen = fullLen-last;
while (--plen>=0) {
result[c++] = groups-1;
}
return result;
}
/**
* Read this var len field and write its contents to the target OutputStream
* @param pipe to read from
* @param out OutputStream to write data to
* @throws IOException
*/
public static > void writeFieldToOutputStream(Pipe pipe, OutputStream out) throws IOException {
int meta = Pipe.takeByteArrayMetaData(pipe);
int length = Pipe.takeByteArrayLength(pipe);
if (length>0) {
int off = bytePosition(meta,pipe,length) & Pipe.blobMask(pipe);
copyFieldToOutputStream(out, length, Pipe.byteBackingArray(meta, pipe), off, pipe.sizeOfBlobRing-off);
}
}
private static void copyFieldToOutputStream(OutputStream out, int length, byte[] backing, int off, int lenFromOffsetToEnd)
throws IOException {
if (lenFromOffsetToEnd>=length) {
//simple add bytes
out.write(backing, off, length);
} else {
//rolled over the end of the buffer
out.write(backing, off, lenFromOffsetToEnd);
out.write(backing, 0, length-lenFromOffsetToEnd);
}
}
/**
* Read from InputStream and write the bytes to the var len field.
* @param pipe data is written into
* @param inputStream InputStream to read data from
* @param byteCount of bytes to be read
* @return true if the input stream is read
* @throws IOException
*/
public static boolean readFieldFromInputStream(Pipe pipe, InputStream inputStream, final int byteCount) throws IOException {
return buildFieldFromInputStream(pipe, inputStream, byteCount, Pipe.getWorkingBlobHeadPosition(pipe), Pipe.blobMask(pipe), Pipe.blob(pipe), pipe.sizeOfBlobRing);
}
private static boolean buildFieldFromInputStream(Pipe pipe, InputStream inputStream, final int byteCount, int startPosition, int byteMask, byte[] buffer, int sizeOfBlobRing) throws IOException {
boolean result = copyFromInputStreamLoop(inputStream, byteCount, startPosition, byteMask, buffer, sizeOfBlobRing, 0);
Pipe.addBytePosAndLen(pipe, startPosition, byteCount);
Pipe.addAndGetBlobWorkingHeadPosition(pipe, byteCount);
assert(Pipe.validateVarLength(pipe, byteCount));
return result;
}
private static boolean copyFromInputStreamLoop(InputStream inputStream, int remaining, int position, int byteMask, byte[] buffer, int sizeOfBlobRing, int size) throws IOException {
while ( (remaining>0) && (size=safeRead(inputStream, position&byteMask, buffer, sizeOfBlobRing, remaining))>=0 ) {
if (size>0) {
remaining -= size;
position += size;
} else {
if (size<0) {
return false;
}
Thread.yield();
}
}
return true;
}
static int safeRead(InputStream inputStream, int position, byte[] buffer, int sizeOfBlobRing, int remaining) throws IOException {
return inputStream.read(buffer, position, safeLength(sizeOfBlobRing, position, remaining) );
}
static int safeRead(DataInput dataInput, int position, byte[] buffer, int sizeOfBlobRing, int remaining) throws IOException {
int safeLength = safeLength(sizeOfBlobRing, position, remaining);
dataInput.readFully(buffer, position, safeLength);
return safeLength;
}
static int safeLength(int sizeOfBlobRing, int position, int remaining) {
return ((position+remaining)<=sizeOfBlobRing) ? remaining : sizeOfBlobRing-position;
}
/**
* ByteBuffers which wrap the backing blob array as 2 buffers
* @param output Pipe target
* @return ByteBuffers array of length 2 wrapping blob array
*/
public static > ByteBuffer[] wrappedWritingBuffers(Pipe output) {
return wrappedWritingBuffers(Pipe.storeBlobWorkingHeadPosition(output),output);
}
/**
* ByteBuffers which wrap the backing blob array as 2 buffers
* @param originalBlobPosition int position of byte data
* @param output Pipe target
* @return ByteBuffers array of length 2 wrapping blob array
*/
public static > ByteBuffer[] wrappedWritingBuffers(int originalBlobPosition, Pipe output) {
return wrappedWritingBuffers(originalBlobPosition, output, output.maxVarLen);
}
/**
* ByteBuffers which wrap the backing blob array as 2 buffers
* @param originalBlobPosition int position of byte data
* @param output Pipe target
* @param maxLen int length which could be written
* @return ByteBuffers array of length 2 wrapping blob array
*/
public static > ByteBuffer[] wrappedWritingBuffers(int originalBlobPosition,
Pipe output, int maxLen) {
assert(maxLen>=0);
int writeToPos = originalBlobPosition & Pipe.blobMask(output); //Get the offset in the blob where we should write
int endPos = writeToPos+maxLen;
ByteBuffer aBuf = output.wrappedBlobWritingRingA; //Get the blob array as a wrapped byte buffer
((Buffer)aBuf).limit(aBuf.capacity());
((Buffer)aBuf).position(writeToPos);
((Buffer)aBuf).limit(Math.min(aBuf.capacity(), endPos ));
ByteBuffer bBuf = output.wrappedBlobWritingRingB; //Get the blob array as a wrapped byte buffer
((Buffer)bBuf).position(0);
((Buffer)bBuf).limit(endPos>output.sizeOfBlobRing ? output.blobMask & endPos: 0);
return output.wrappedWritingBuffers;
}
/**
* Record the number of bytes written after direct manipulation of the backing array.
* This method is not needed if the normal access methods are used.
*
* @param len int length of bytes written
* @param output Pipe target
* @param schema
*/
public static > void moveBlobPointerAndRecordPosAndLength(int len, Pipe output) {
moveBlobPointerAndRecordPosAndLength(Pipe.unstoreBlobWorkingHeadPosition(output), len, output);
}
/**
* Record the number of bytes written after direct manipulation of the backing array.
* This method is not needed if the normal access methods are used.
*
* @param originalBlobPosition int head position where the write started
* @param len int length of bytes written
* @param output Pipe target
*/
public static > void moveBlobPointerAndRecordPosAndLength(int originalBlobPosition, int len, Pipe output) {
assert(verifyHasRoomForWrite(len, output));
//blob head position is moved forward
if (len>0) { //len can be 0 so do nothing, len can be -1 for eof also nothing to move forward
Pipe.addAndGetBlobWorkingHeadPosition(output, len);
}
//record the new start and length to the slab for this blob
Pipe.addBytePosAndLen(output, originalBlobPosition, len);
}
private static > boolean verifyHasRoomForWrite(int len, Pipe output) {
int h = getWorkingBlobHeadPosition(output)&output.blobMask;
int t = getBlobTailPosition(output)&output.blobMask;
int consumed;
if (h>=t) {
consumed = len+(h-t);
assert(consumed<=output.blobMask) : "length too large for existing data, proposed addition "+len+" head "+h+" tail "+t+" "+output+" "+Pipe.contentRemaining(output);
} else {
consumed = len+h+(output.sizeOfBlobRing-t);
assert(consumed<=output.blobMask) : "length is too large for existing data "+len+" + t:"+t+" h:"+h+" max "+output.blobMask;
}
return (consumed<=output.blobMask);
}
/**
* Wrapped byte buffer for reading the backing ring.
*
* @param pipe Pipe source pipe
* @param meta int backing meta data
* @param len int length to be read
* @return ByteBufer wrapping the blob array
*/
public static > ByteBuffer wrappedBlobReadingRingA(Pipe pipe, int meta, int len) {
ByteBuffer buffer;
if (meta < 0) {
buffer = wrappedBlobConstBuffer(pipe);
int position = PipeReader.POS_CONST_MASK & meta;
((Buffer)buffer).position(position);
((Buffer)buffer).limit(position+len);
} else {
buffer = wrappedBlobRingA(pipe);
int position = pipe.blobMask & restorePosition(pipe,meta);
((Buffer)buffer).clear();
((Buffer)buffer).position(position);
//use the end of the buffer if the length runs past it.
((Buffer)buffer).limit(Math.min(pipe.sizeOfBlobRing, position+len));
}
return buffer;
}
/**
* Wrapped byte buffer for reading the backing ring.
*
* @param pipe Pipe source pipe
* @param meta int backing meta data
* @param len int length to be read
* @return ByteBufer wrapping the blob array
*/
public static > ByteBuffer wrappedBlobReadingRingB(Pipe pipe, int meta, int len) {
ByteBuffer buffer;
if (meta < 0) {
//always zero because constant array never wraps
buffer = wrappedBlobConstBuffer(pipe);
((Buffer)buffer).position(0);
((Buffer)buffer).limit(0);
} else {
buffer = wrappedBlobRingB(pipe);
int position = pipe.blobMask & restorePosition(pipe,meta);
((Buffer)buffer).clear();
//position is zero
int endPos = position+len;
if (endPos>pipe.sizeOfBlobRing) {
((Buffer)buffer).limit(pipe.blobMask & endPos);
} else {
((Buffer)buffer).limit(0);
}
}
return buffer;
}
/**
* Wrapped byte buffer array of 2 for reading the backing ring.
*
* @param pipe Pipe source pipe
* @param meta int backing meta data
* @param len int length to be read
* @return ByteBufer array wrapping the blob array
*/
public static > ByteBuffer[] wrappedReadingBuffers(Pipe pipe, int meta, int len) {
if (meta >= 0) {
//MUST call bytePosition method which creates side effect of assuming this data is consumed
positionedReadingBuffers(pipe, len,
pipe.blobMask & bytePosition(meta,pipe,len),
wrappedBlobRingA(pipe),
wrappedBlobRingB(pipe));
} else {
wrappedReadingBuffersConst(pipe, meta, len);
}
return pipe.wrappedReadingBuffers;
}
public static > ByteBuffer[] wrappedReadingDirectBuffers(Pipe pipe, int meta, int len) {
if (null != pipe.directBlob) {
if (meta >= 0) {
//MUST call bytePosition method which creates side effect of assuming this data is consumed
positionedReadingBuffers(pipe, len,
pipe.blobMask & bytePosition(meta,pipe,len),
pipe.directBlobReaderA,
pipe.directBlobReaderB);
} else {
throw new UnsupportedOperationException();
}
return pipe.wrappedReadingDirectBuffers;
} else {
return wrappedReadingBuffers(pipe,meta,len);
}
}
static > ByteBuffer[] positionedReadingBuffers(Pipe pipe, int len,
final int position, ByteBuffer aBuf, ByteBuffer bBuf) {
final int endPos = position+(len>=0?len:0);
((Buffer)aBuf).clear();
((Buffer)aBuf).position(position);
//use the end of the buffer if the length runs past it.
((Buffer)aBuf).limit(Math.min(pipe.sizeOfBlobRing, endPos));
((Buffer)bBuf).clear();
((Buffer)bBuf).limit(endPos > pipe.sizeOfBlobRing ? pipe.blobMask & endPos : 0 );
return pipe.wrappedReadingBuffers;
}
static > ByteBuffer[] wrappedReadingBuffersConst(Pipe pipe, int meta, int len) {
ByteBuffer aBuf = wrappedBlobConstBuffer(pipe);
int position = PipeReader.POS_CONST_MASK & meta;
aBuf.position(position);
aBuf.limit(position+len);
//always zero because constant array never wraps
ByteBuffer bBuf = wrappedBlobConstBuffer(pipe);
bBuf.position(0);
bBuf.limit(0);
return pipe.wrappedReadingBuffers;
}
/**
* Converts chars in an array to bytes based on UTF8 encoding.
*
* @param charSeq backing array of chars
* @param charSeqOff offset where to consume chars
* @param charSeqLength length of chars to consume
* @param targetBuf target array
* @param targetIdx offset where to start writing
* @param targetMask mask for looping the target buffer
* @return int length of encoded bytes
*/
public static int convertToUTF8(final char[] charSeq, final int charSeqOff, final int charSeqLength,
final byte[] targetBuf, final int targetIdx, final int targetMask) {
int target = targetIdx;
int c = 0;
while (c < charSeqLength) {
target = encodeSingleChar((int) charSeq[charSeqOff+c++], targetBuf, targetMask, target);
}
//NOTE: the above loop will keep looping around the target buffer until done and will never cause an array out of bounds.
// the length returned however will be larger than targetMask, this should be treated as an error.
return target-targetIdx;//length;
}
/**
* Converts chars in an array to bytes based on UTF8 encoding.
*
* @param charSeq CharSequence source chars
* @param charSeqOff source offset to read from
* @param charSeqLength source length to read
* @param targetBuf target array
* @param targetIdx offset where to start writing
* @param targetMask mask for looping the target buffer
* @return int length of encoded bytes
*/
public static int convertToUTF8(final CharSequence charSeq, final int charSeqOff, final int charSeqLength,
final byte[] targetBuf, final int targetIdx, final int targetMask) {
/**
*
* Converts CharSequence (base class of String) into UTF-8 encoded bytes and writes those bytes to an array.
* The write loops around the end using the targetMask so the returned length must be checked after the call
* to determine if and overflow occurred.
*
* Due to the variable nature of converting chars into bytes there is not easy way to know before walking how
* many bytes will be needed. To prevent any overflow ensure that you have 6*lengthOfCharSequence bytes available.
*
*/
int target = targetIdx;
int c = 0;
while (c < charSeqLength) {
target = encodeSingleChar((int) charSeq.charAt(charSeqOff+c++), targetBuf, targetMask, target);
}
//NOTE: the above loop will keep looping around the target buffer until done and will never cause an array out of bounds.
// the length returned however will be larger than targetMask, this should be treated as an error.
return target-targetIdx;//length;
}
/**
* Debug write of this fragment to the target appendable
*
* @param input Pipe source
* @param target Appendable to write text
* @param fragIdx fragment idx
* @param message schema
*/
public static > void appendFragment(Pipe input, Appendable target, int fragIdx) {
try {
FieldReferenceOffsetManager from = from(input);
int fields = from.fragScriptSize[fragIdx];
assert (fragIdx0 || !input.ringWalker.isNewMessage) {
int pos = from.fragDataSize[i+fragIdx];
//create string values of each field so we can see them easily
switch (type) {
case TypeMask.Group:
int oper = TokenBuilder.extractOper(token);
boolean open = (0==(OperatorMask.Group_Bit_Close&oper));
value = "open:"+open+" pos:"+p;
break;
case TypeMask.GroupLength:
int len = readInt(slab(input), input.slabMask, pos+tailPosition(input));
value = Integer.toHexString(len)+"("+len+")";
break;
case TypeMask.IntegerSigned:
case TypeMask.IntegerUnsigned:
case TypeMask.IntegerSignedOptional:
case TypeMask.IntegerUnsignedOptional:
int readInt = readInt(slab(input), input.slabMask, pos+tailPosition(input));
value = Integer.toHexString(readInt)+"("+readInt+")";
break;
case TypeMask.LongSigned:
case TypeMask.LongUnsigned:
case TypeMask.LongSignedOptional:
case TypeMask.LongUnsignedOptional:
long readLong = readLong(slab(input), input.slabMask, pos+tailPosition(input));
value = Long.toHexString(readLong)+"("+readLong+")";
break;
case TypeMask.Decimal:
case TypeMask.DecimalOptional:
int exp = readInt(slab(input), input.slabMask, pos+tailPosition(input));
long mantissa = readLong(slab(input), input.slabMask, pos+tailPosition(input)+1);
value = exp+" "+mantissa;
break;
case TypeMask.TextASCII:
case TypeMask.TextASCIIOptional:
{
int meta = readInt(slab(input), input.slabMask, pos+tailPosition(input));
int length = readInt(slab(input), input.slabMask, pos+tailPosition(input)+1);
readASCII(input, target, meta, length);
value = meta+" len:"+length;
// value = target.toString();
}
break;
case TypeMask.TextUTF8:
case TypeMask.TextUTF8Optional:
{
int meta = readInt(slab(input), input.slabMask, pos+tailPosition(input));
int length = readInt(slab(input), input.slabMask, pos+tailPosition(input)+1);
readUTF8(input, target, meta, length);
value = meta+" len:"+length;
// value = target.toString();
}
break;
case TypeMask.ByteVector:
case TypeMask.ByteVectorOptional:
{
int meta = readInt(slab(input), input.slabMask, pos+tailPosition(input));
int length = readInt(slab(input), input.slabMask, pos+tailPosition(input)+1);
value = meta+" len:"+length;
}
break;
default: target.append("unknown ").append("\n");
}
value += (" "+TypeMask.toString(type)+" "+pos);
}
target.append(" ").append(name).append(":");
Appendables.appendValue(target, id);
target.append(" ").append(value).append("\n");
if (TypeMask.Decimal==type || TypeMask.DecimalOptional==type) {
i++;//skip second slot for decimals
}
i++;
}
} catch (IOException ioe) {
PipeReader.log.error("Unable to build text for fragment.",ioe);
throw new RuntimeException(ioe);
}
}
/**
* Read var length field into a target ByteBuffer
*
* @param pipe Pipe source
* @param target ByteBuffer written into
* @param meta int field meta data
* @param len int field length in bytes
* @param message schema
* @return ByteBuffer target
*/
public static > ByteBuffer readBytes(Pipe pipe, ByteBuffer target, int meta, int len) {
if (meta >= 0) {
return readBytesRing(pipe,len,target,restorePosition(pipe,meta));
} else {
return readBytesConst(pipe,len,target,PipeReader.POS_CONST_MASK & meta);
}
}
/**
* Read var length field into a DataOutputBlobWriter, eg another field.
*
* @param pipe Pipe source
* @param target DataOutputBlobWriter field to write into
* @param meta int field meta data
* @param len int field length in bytes
* @return DataOutputBlobWriter target;
*/
public static > DataOutputBlobWriter> readBytes(Pipe pipe,
DataOutputBlobWriter> target,
int meta, int len) {
if (meta >= 0) {
return readBytesRing(pipe,len,target,restorePosition(pipe,meta));
} else {
return readBytesConst(pipe,len,target,PipeReader.POS_CONST_MASK & meta);
}
}
/**
* Read var length field from the pipe, position must be set up first. Data is written to target field.
*
* @param pipe Pipe source
* @param target DataOutputBlobWriter field written into
* @param message schema
* @return DataOutputBlobWriter target
*/
public static > DataOutputBlobWriter> readBytes(Pipe pipe,
DataOutputBlobWriter> target) {
return Pipe.readBytes(pipe, target, Pipe.takeByteArrayMetaData(pipe), Pipe.takeByteArrayLength(pipe));
}
/**
* Reads bytes from specified pipe at given index
* @param pipe to read from
* @param target array to check
* @param targetIdx index to check
* @param message schema
*/
public static > void readBytes(Pipe pipe, byte[] target, int targetIdx, int targetMask, int meta, int len) {
if (meta >= 0) {
copyBytesFromToRing(pipe.blobRing,restorePosition(pipe,meta),pipe.blobMask,target,targetIdx,targetMask,len);
} else {
//NOTE: constByteBuffer does not wrap so we do not need the mask
copyBytesFromToRing(pipe.blobConstBuffer, PipeReader.POS_CONST_MASK & meta, 0xFFFFFFFF, target, targetIdx, targetMask, len);
}
}
private static > ByteBuffer readBytesRing(Pipe pipe, int len, ByteBuffer target, int pos) {
int mask = pipe.blobMask;
byte[] buffer = pipe.blobRing;
int tStart = pos & mask;
int len1 = 1+mask - tStart;
if (len1>=len) {
target.put(buffer, mask&pos, len);
} else {
target.put(buffer, mask&pos, len1);
target.put(buffer, 0, len-len1);
}
return target;
}
private static > DataOutputBlobWriter> readBytesRing(Pipe pipe, int len, DataOutputBlobWriter> target, int pos) {
DataOutputBlobWriter.write(target, pipe.blobRing, pos, len, pipe.blobMask);
//could use this code to make something similar for writing to DataOutput or output stream?
// int mask = pipe.blobMask;
// byte[] buffer = pipe.blobRing;
// int len1 = 1+mask - (pos & mask);
//
// if (len1>=len) {
// target.write(buffer, mask&pos, len);
// } else {
// target.write(buffer, mask&pos, len1);
// target.write(buffer, 0, len-len1);
// }
return target;
}
private static > ByteBuffer readBytesConst(Pipe pipe, int len, ByteBuffer target, int pos) {
target.put(pipe.blobConstBuffer, pos, len);
return target;
}
private static > DataOutputBlobWriter> readBytesConst(Pipe pipe, int len, DataOutputBlobWriter> target, int pos) {
target.write(pipe.blobConstBuffer, pos, len);
return target;
}
/**
* Reads ASCII characters at given section of a pipe
* @param pipe to read from
* @param target area to read from
* @param len length of area to read from
* @return ASCII characters read
*/
public static , A extends Appendable> A readASCII(Pipe pipe, A target, int meta, int len) {
if (meta < 0) {//NOTE: only uses const for const or default, may be able to optimize away this conditional.
return readASCIIConst(pipe,len,target,PipeReader.POS_CONST_MASK & meta);
} else {
return readASCIIRing(pipe,len,target,restorePosition(pipe, meta));
}
}
/**
* Reads ASCII characters or an optional null at given section of a pipe
* @param pipe Pipe source
* @param target Appendable destination
* @param meta int meta data for the field
* @param len int length for the field
* @return Appendable target
*/
public static , A extends Appendable> A readOptionalASCII(Pipe pipe, A target,
int meta, int len) {
if (len<0) {
return null;
}
if (meta < 0) {//NOTE: only useses const for const or default, may be able to optimize away this conditional.
return readASCIIConst(pipe,len,target,PipeReader.POS_CONST_MASK & meta);
} else {
return readASCIIRing(pipe,len,target,restorePosition(pipe, meta));
}
}
/**
* skip over the next fragment and position for reading after that fragment
*
* @param pipe Pipe source
*/
public static > void skipNextFragment(Pipe pipe) {
skipNextFragment(pipe, Pipe.takeMsgIdx(pipe));
}
/**
* Skips over specified section of the pipe
* @param pipe that you're reading from
* @param msgIdx int fragment idx already read of the current fragment to be skipped
*/
public static > void skipNextFragment(Pipe pipe, int msgIdx) {
long pos = Pipe.getWorkingTailPosition(pipe);
int msgSize = Pipe.sizeOf(pipe, msgIdx);
int idx = (int)(pos+msgSize-2);
int msgBytesConsumed = Pipe.slab(pipe)[ Pipe.slabMask(pipe) & idx ];
//position for the bytes consumed is stepped over and we have already moved forward by size of messageIdx header so substract 2.
pipe.slabRingTail.workingTailPos.value += (msgSize-2);
pipe.blobRingTail.byteWorkingTailPos.value = pipe.blobMask & (msgBytesConsumed + pipe.blobRingTail.byteWorkingTailPos.value);
Pipe.confirmLowLevelRead(pipe, msgSize);
Pipe.releaseReadLock(pipe);
}
/**
* Checks if given CharSequence is equal to data in a given area
* @param pipe used in comparison
* @param charSeq CharSequence to compare
* @param meta TODO: ??
* @param len TODO: ??
* @return true if they are equal
*/
public static > boolean isEqual(Pipe pipe, CharSequence charSeq, int meta, int len) {
if (len!=charSeq.length()) {
return false;
}
if (meta < 0) {
int pos = PipeReader.POS_CONST_MASK & meta;
byte[] buffer = pipe.blobConstBuffer;
assert(null!=buffer) : "If constants are used the constByteBuffer was not initialized. Otherwise corruption in the stream has been discovered";
while (--len >= 0) {
if (charSeq.charAt(len)!=buffer[pos+len]) {
return false;
}
}
} else {
byte[] buffer = pipe.blobRing;
int mask = pipe.blobMask;
int pos = restorePosition(pipe, meta);
while (--len >= 0) {
if (charSeq.charAt(len)!=buffer[mask&(pos+len)]) {
return false;
}
}
}
return true;
}
/**
* checks for equals bytes
*
* @param pipe Pipe source
* @param expected byte[] backing array
* @param expectedPos int position in backing array
* @param meta int field meta data
* @param len int length of field
* @return boolean true if equal content
*/
public static > boolean isEqual(Pipe pipe, byte[] expected, int expectedPos, int meta, int len) {
if (len>(expected.length-expectedPos)) {
return false;
}
if (meta < 0) {
int pos = PipeReader.POS_CONST_MASK & meta;
byte[] buffer = pipe.blobConstBuffer;
assert(null!=buffer) : "If constants are used the constByteBuffer was not initialized. Otherwise corruption in the stream has been discovered";
while (--len >= 0) {
if (expected[expectedPos+len]!=buffer[pos+len]) {
return false;
}
}
} else {
byte[] buffer = pipe.blobRing;
int mask = pipe.blobMask;
int pos = restorePosition(pipe, meta);
while (--len >= 0) {
if (expected[expectedPos+len]!=buffer[mask&(pos+len)]) {
return false;
}
}
}
return true;
}
/**
* Check if both segments of bytes are equal
*
* @param aBack byte[] backing array
* @param aPos int position to read from in backing array a
* @param aMask int mask for looping backing array a
* @param bBack byte[] backing array
* @param bPos int position to read from in backing array b
* @param bMask int mask for looping backing array b
* @param len int length of segment
* @return boolean true if content equals
*/
public static boolean isEqual(byte[] aBack, int aPos, int aMask,
byte[] bBack, int bPos, int bMask, int len) {
while (--len>=0) {
byte a = aBack[(aPos+len)&aMask];
byte b = bBack[(bPos+len)&bMask];
if (a!=b) {
return false;
}
}
return true;
}
private static , A extends Appendable> A readASCIIRing(Pipe pipe, int len, A target, int pos) {
byte[] buffer = pipe.blobRing;
int mask = pipe.blobMask;
try {
while (--len >= 0) {
target.append((char)buffer[mask & pos++]);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return target;
}
private static , A extends Appendable> A readASCIIConst(Pipe pipe, int len, A target, int pos) {
try {
byte[] buffer = pipe.blobConstBuffer;
assert(null!=buffer) : "If constants are used the constByteBuffer was not initialized. Otherwise corruption in the stream has been discovered";
while (--len >= 0) {
target.append((char)buffer[pos++]);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return target;
}
/**
* Reads UTF8 characters from specified Pipe
* @param pipe pipe to read from
* @param target section of pipe to read from
* @param len number of characters to read
* @param MessageSchema to extend
* @param Appendable to extend
* @return TODO: unsure of return
*/
public static , A extends Appendable> A readUTF8(Pipe pipe, A target, int meta, int len) {
if (meta < 0) {//NOTE: only uses const for const or default, may be able to optimize away this conditional.
return (A) readUTF8Const(pipe,len,target,PipeReader.POS_CONST_MASK & meta);
} else {
return (A) readUTF8Ring(pipe,len,target,restorePosition(pipe,meta));
}
}
/**
* Reads UTF8 characters or a null value from the specified pipe
*
* @param pipe Pipe source
* @param target Appendable destination
* @param meta int meta data for the field
* @param len int length for the field
* @return Appendable target
*/
public static > Appendable readOptionalUTF8(Pipe pipe, Appendable target, int meta, int len) {
if (len<0) {
return null;
}
if (meta < 0) {//NOTE: only uses const for const or default, may be able to optimize away this conditional.
return readUTF8Const(pipe,len,target,PipeReader.POS_CONST_MASK & meta);
} else {
return readUTF8Ring(pipe,len,target,restorePosition(pipe,meta));
}
}
private static > Appendable readUTF8Const(Pipe pipe, int bytesLen, Appendable target, int ringPos) {
try{
long charAndPos = ((long)ringPos)<<32;
long limit = ((long)ringPos+bytesLen)<<32;
while (charAndPos> Appendable readUTF8Ring(Pipe pipe, int bytesLen, Appendable target, int ringPos) {
try{
long charAndPos = ((long)ringPos)<<32;
long limit = ((long)ringPos+bytesLen)<<32;
while (charAndPos MessageSchema to write to
*/
public static > void addDecimalAsASCII(
int readDecimalExponent,
long readDecimalMantissa,
Pipe outputRing) {
DataOutputBlobWriter out = outputRing.openOutputStream(outputRing);
Appendables.appendDecimalValue(out, readDecimalMantissa, (byte)readDecimalExponent);
DataOutputBlobWriter.closeLowLevelField(out);
}
/**
* safe addition to position by masking early to ensure this value does not become negative.
*
* @param pos int original position
* @param value long length to be added
* @return int masked position, will not be negative
*/
public static int safeBlobPosAdd(int pos, long value) {
return (int)(Pipe.BYTES_WRAP_MASK&(pos+value));
}
/**
* All bytes even those not yet committed.
*
* @param ringBuffer
* @return total bytes count in content
*/
public static > int bytesOfContent(Pipe ringBuffer) {
int dif = (ringBuffer.blobMask&ringBuffer.blobRingHead.byteWorkingHeadPos.value) - (ringBuffer.blobMask&PaddedInt.get(ringBuffer.blobRingTail.bytesTailPos));
return ((dif>>31)< schema
* @param pipe Pipe target
* @param size int batch size
*/
public static > void validateBatchSize(Pipe pipe, int size) {
if (null != Pipe.from(pipe)) {
int maxBatch = computeMaxBatchSize(pipe);
if (size>maxBatch) {
throw new UnsupportedOperationException("For the configured pipe buffer the batch size can be no larger than "+maxBatch);
}
}
}
/**
* maximum batch size based on the Pipe configuration
*
* @param schema
* @param pipe Pipe source
* @return int max batch size
*/
public static > int computeMaxBatchSize(Pipe pipe) {
return computeMaxBatchSize(pipe,2);//default mustFit of 2
}
/**
* maximum batch size based on pipe and must fit batches count
*
* @param schema
* @param pipe Pipe source
* @param mustFit how many batches must fit on the pipe
* @return max batch size
*/
public static > int computeMaxBatchSize(Pipe pipe, int mustFit) {
assert(mustFit>=1);
int maxBatchFromBytes = pipe.maxVarLen==0?Integer.MAX_VALUE:(pipe.sizeOfBlobRing/pipe.maxVarLen)/mustFit;
int maxBatchFromPrimary = (pipe.sizeOfSlabRing/FieldReferenceOffsetManager.maxFragmentSize(from(pipe)))/mustFit;
return Math.min(maxBatchFromBytes, maxBatchFromPrimary);
}
/**
* Checks to see if the end of the pipe has been reached
* @param pipe MessageSchema to be extended
* @param tailPosition position of assumed end of pipe
* @param pipe to be checked
* @return true
if end of pipe reached, else false
*/
public static > boolean isEndOfPipe(Pipe pipe, long tailPosition) {
return tailPosition>=pipe.knownPositionOfEOF;
}
/**
* Publish an EOF value to all the outgoing pipes
*
* @param pipe Pipe[] targets
*/
public static void publishEOF(Pipe>[] pipe) {
int i = pipe.length;
while (--i>=0) {
if (null != pipe[i] && Pipe.isInit(pipe[i])) {
publishEOF(pipe[i]);
}
}
}
/**
* Publish EOF message to the target pipe
*
* @param pipe Pipe
*/
public static > void publishEOF(Pipe pipe) {
if (pipe.slabRingTail.tailPos.get()+pipe.sizeOfSlabRing>=pipe.slabRingHead.headPos.get()+Pipe.EOF_SIZE) {
PaddedInt.set(pipe.blobRingHead.bytesHeadPos,pipe.blobRingHead.byteWorkingHeadPos.value);
pipe.knownPositionOfEOF = (int)pipe.slabRingHead.workingHeadPos.value + from(pipe).templateOffset;
pipe.slabRing[pipe.slabMask & (int)pipe.knownPositionOfEOF] = -1;
pipe.slabRing[pipe.slabMask & ((int)pipe.knownPositionOfEOF+1)] = 0;
pipe.slabRingHead.headPos.lazySet(pipe.slabRingHead.workingHeadPos.value = pipe.slabRingHead.workingHeadPos.value + Pipe.EOF_SIZE);
assert(Pipe.contentRemaining(pipe)<=pipe.sizeOfSlabRing) : "distance between tail and head must not be larger than the ring, internal error. "+pipe;
} else {
log.error("Unable to send EOF, the outgoing pipe is 100% full, downstream stages may not get closed.\n"
+ "To resolve this issue ensure the outgoing pipe has room for write before calling this.\n"+pipe, new Exception("check for pipe data"));
}
}
/**
* Copies bytes from specified location to Ring
* @param source data to be copied
* @param sourceloc location of data to be copied
* @param targetloc location to copy data to
*/
public static void copyBytesFromToRing(byte[] source, int sourceloc, int sourceMask, byte[] target, int targetloc, int targetMask, int length) {
//assert (sourceMask!=Integer.MAX_VALUE) : "for source do not call this method without a mask";
//assert (targetMask!=Integer.MAX_VALUE) : "for target do not call this method without a mask";
copyBytesFromToRingMasked(source, sourceloc & sourceMask, (sourceloc + length) & sourceMask, target, targetloc & targetMask, (targetloc + length) & targetMask, length);
}
/**
* Copies ints from specified location to Ring
* @param source data to be copied
* @param sourceloc location of data to be copied
* @param targetloc location to copy data to
*/
public static void copyIntsFromToRing(int[] source, int sourceloc, int sourceMask, int[] target, int targetloc, int targetMask, int length) {
copyIntsFromToRingMasked(source, sourceloc & sourceMask, (sourceloc + length) & sourceMask, target, targetloc & targetMask, (targetloc + length) & targetMask, length);
}
/**
* Copy bytes from a non-wrapping array into a ring buffer (an array which wraps)
* @param source byte[] source array
* @param sourceloc int source location
* @param target byte[] target array
* @param targetloc int target location
* @param targetMask int target mask for looping over target array
* @param length int length in bytes to copy
*/
public static void copyBytesFromArrayToRing(byte[] source, final int sourceloc, byte[] target, int targetloc, int targetMask, int length) {
///NOTE: the source can never wrap..
if (length > 0) {
final int tStart = targetloc & targetMask;
final int tStop = (targetloc + length) & targetMask;
if (tStop > tStart) {
//the source and target do not wrap
System.arraycopy(source, sourceloc, target, tStart, length);
} else {
//the source does not wrap but the target does
// done as two copies
System.arraycopy(source, sourceloc, target, tStart, length-tStop);
System.arraycopy(source, sourceloc + length - tStop, target, 0, tStop);
}
}
}
public static void copyBytesFromInputStreamToRing(InputStream source, byte[] target, int targetloc, int targetMask, int length) {
try {
if (length > 0) {
final int tStart = targetloc & targetMask;
final int tStop = (targetloc + length) & targetMask;
if (tStop > tStart) {
//the source and target do not wrap
source.read(target, tStart, length);
} else {
//the source does not wrap but the target does
// done as two copies
source.read(target, tStart, length-tStop);
source.read(target, 0, tStop);
}
}
} catch (IOException ioex) {
throw new RuntimeException(ioex);
}
}
/**
* directly copy byte array field from the source pipe to the target pipe
* @param source Pipe source positioned to the field to be copied
* @param target Pipe target positioned to where the field is to be written
*/
public static , T extends MessageSchema> void addByteArray(Pipe source, Pipe target) {
int sourceMeta = Pipe.takeByteArrayMetaData(source);
int sourceLen = Pipe.takeByteArrayLength(source);
Pipe.validateVarLength(target, sourceLen);
Pipe.copyBytesFromToRing(Pipe.byteBackingArray(sourceMeta, source),
Pipe.bytePosition(sourceMeta, source, sourceLen),
Pipe.blobMask(source),
target.blobRing,
target.blobRingHead.byteWorkingHeadPos.value,
target.blobMask,
sourceLen);
Pipe.addBytePosAndLen(target, target.blobRingHead.byteWorkingHeadPos.value, sourceLen);
target.blobRingHead.byteWorkingHeadPos.value = Pipe.BYTES_WRAP_MASK&(target.blobRingHead.byteWorkingHeadPos.value + sourceLen);
}
private static void copyBytesFromToRingMasked(byte[] source,
final int rStart, final int rStop, byte[] target, final int tStart,
final int tStop, int length) {
if (length > 0) {
if (tStop > tStart) {
//do not accept the equals case because this can not work with data the same length as as the buffer
doubleMaskTargetDoesNotWrap(source, rStart, rStop, target, tStart, length);
} else {
doubleMaskTargetWraps(source, rStart, rStop, target, tStart, tStop, length);
}
}
}
private static void copyIntsFromToRingMasked(int[] source,
final int rStart, final int rStop, int[] target, final int tStart,
final int tStop, int length) {
if (tStop > tStart) {
doubleMaskTargetDoesNotWrap(source, rStart, rStop, target, tStart, length);
} else {
doubleMaskTargetWraps(source, rStart, rStop, target, tStart, tStop, length);
}
}
private static void doubleMaskTargetDoesNotWrap(byte[] source,
final int srcStart, final int srcStop, byte[] target, final int trgStart, int length) {
if (srcStop >= srcStart) {
//the source and target do not wrap
System.arraycopy(source, srcStart, target, trgStart, length);
} else {
//the source is wrapping but not the target
System.arraycopy(source, srcStart, target, trgStart, length-srcStop);
System.arraycopy(source, 0, target, trgStart + length - srcStop, srcStop);
}
}
private static void doubleMaskTargetDoesNotWrap(int[] source,
final int rStart, final int rStop, int[] target, final int tStart,
int length) {
if (rStop > rStart) {
//the source and target do not wrap
System.arraycopy(source, rStart, target, tStart, length);
} else {
//the source is wrapping but not the target
System.arraycopy(source, rStart, target, tStart, length-rStop);
System.arraycopy(source, 0, target, tStart + length - rStop, rStop);
}
}
private static void doubleMaskTargetWraps(byte[] source, final int rStart,
final int rStop, byte[] target, final int tStart, final int tStop,
int length) {
if (rStop > rStart) {
// //the source does not wrap but the target does
// // done as two copies
System.arraycopy(source, rStart, target, tStart, length-tStop);
System.arraycopy(source, rStart + length - tStop, target, 0, tStop);
} else {
if (length>0) {
//both the target and the source wrap
doubleMaskDoubleWrap(source, target, length, tStart, rStart, length-tStop, length-rStop);
}
}
}
private static void doubleMaskTargetWraps(int[] source, final int rStart,
final int rStop, int[] target, final int tStart, final int tStop,
int length) {
if (rStop > rStart) {
// //the source does not wrap but the target does
// // done as two copies
System.arraycopy(source, rStart, target, tStart, length-tStop);
System.arraycopy(source, rStart + length - tStop, target, 0, tStop);
} else {
if (length>0) {
//both the target and the source wrap
doubleMaskDoubleWrap(source, target, length, tStart, rStart, length-tStop, length-rStop);
}
}
}
private static void doubleMaskDoubleWrap(byte[] source, byte[] target,
int length, final int tStart, final int rStart, int targFirstLen,
int srcFirstLen) {
if (srcFirstLen> int leftConvertIntToASCII(
Pipe pipe,
int value,
int idx) {
//max places is value for -2B therefore its 11 places so we start out that far and work backwards.
//this will leave a gap but that is not a problem.
byte[] target = pipe.blobRing;
int tmp = Math.abs(value);
int max = idx;
do {
//do not touch these 2 lines they make use of secret behavior in hot spot that does a single divide.
int t = tmp/10;
int r = tmp%10;
target[pipe.blobMask&--idx] = (byte)('0'+r);
tmp = t;
} while (0!=tmp);
target[pipe.blobMask& (idx-1)] = (byte)'-';
//to make it positive we jump over the sign.
idx -= (1&(value>>31));
//shift it down to the head
int length = max-idx;
if (idx!=pipe.blobRingHead.byteWorkingHeadPos.value) {
int s = 0;
while (s> int leftConvertLongToASCII(Pipe pipe, long value, int idx) {
//max places is value for -2B therefore its 11 places so we start out that far and work backwards.
//this will leave a gap but that is not a problem.
byte[] target = pipe.blobRing;
long tmp = Math.abs(value);
int max = idx;
do {
//do not touch these 2 lines they make use of secret behavior in hot spot that does a single divide.
long t = tmp/10;
long r = tmp%10;
target[pipe.blobMask&--idx] = (byte)('0'+r);
tmp = t;
} while (0!=tmp);
target[pipe.blobMask& (idx-1)] = (byte)'-';
//to make it positive we jump over the sign.
idx -= (1&(value>>63));
int length = max-idx;
//shift it down to the head
if (idx!=pipe.blobRingHead.byteWorkingHeadPos.value) {
int s = 0;
while (s> 32);
byte b;
if ((b = source[mask&sourcePos++]) >= 0) {
// code point 7
return (((long)sourcePos)<<32) | (long)b; //1 byte result of 7 bits with high zero
}
int result;
if (((byte) (0xFF & (b << 2))) >= 0) {
if ((b & 0x40) == 0) {
++sourcePos;
return (((long)sourcePos)<<32) | 0xFFFD; // Bad data replacement char
}
// code point 11
result = (b & 0x1F); //5 bits
} else {
if (((byte) (0xFF & (b << 3))) >= 0) {
// code point 16
result = (b & 0x0F); //4 bits
} else {
if (((byte) (0xFF & (b << 4))) >= 0) {
// code point 21
result = (b & 0x07); //3 bits
} else {
if (((byte) (0xFF & (b << 5))) >= 0) {
// code point 26
result = (b & 0x03); // 2 bits
} else {
if (((byte) (0xFF & (b << 6))) >= 0) {
// code point 31
result = (b & 0x01); // 1 bit
} else {
// the high bit should never be set
sourcePos += 5;
return (((long)sourcePos)<<32) | 0xFFFD; // Bad data replacement char
}
if ((source[mask&sourcePos] & 0xC0) != 0x80) {
sourcePos += 5;
return (((long)sourcePos)<<32) | 0xFFFD; // Bad data replacement char
}
result = (result << 6) | (int)(source[mask&sourcePos++] & 0x3F);
}
if ((source[mask&sourcePos] & 0xC0) != 0x80) {
sourcePos += 4;
return (((long)sourcePos)<<32) | 0xFFFD; // Bad data replacement char
}
result = (result << 6) | (int)(source[mask&sourcePos++] & 0x3F);
}
if ((source[mask&sourcePos] & 0xC0) != 0x80) {
sourcePos += 3;
return (((long)sourcePos)<<32) | 0xFFFD; // Bad data replacement char
}
result = (result << 6) | (int)(source[mask&sourcePos++] & 0x3F);
}
if ((source[mask&sourcePos] & 0xC0) != 0x80) {
sourcePos += 2;
return (((long)sourcePos)<<32) | 0xFFFD; // Bad data replacement char
}
result = (result << 6) | (int)(source[mask&sourcePos++] & 0x3F);
}
if ((source[mask&sourcePos] & 0xC0) != 0x80) {
log.error("Invalid encoding, low byte must have bits of 10xxxxxx but we find {}. conclusion: this data was not UTF8 encoded.",Integer.toBinaryString(source[mask&sourcePos]),new Exception("Check for pipe corruption or unprintable data"));
sourcePos += 1;
return (((long)sourcePos)<<32) | 0xFFFD; // Bad data replacement char
}
long chr = ((result << 6) | (int)(source[mask&sourcePos++] & 0x3F)); //6 bits
return (((long)sourcePos)<<32) | chr;
}
/**
* copy ASCII to bytes
* @param source CharSequence char source
* @param pipe Pipe target to write bytes field
* @return int new position after write
*/
public static > int copyASCIIToBytes(CharSequence source, Pipe pipe) {
return copyASCIIToBytes(source, 0, source.length(), pipe);
}
/**
* Writes ASCII to specified pipe
* @param source characters to write
* @param rb pipe to write to
* @param MessageSchema to extend
*/
public static > void addASCII(CharSequence source, Pipe rb) {
addASCII(source, 0, null==source ? -1 : source.length(), rb);
}
/**
* Add these chars as ASCII values to output pipe
* @param source CharSequence chars
* @param sourceIdx position to start reading chars
* @param sourceCharCount count of chars to write
* @param pipe Pipe target to add byte field into
*/
public static > void addASCII(
CharSequence source, int sourceIdx,
int sourceCharCount, Pipe pipe) {
addBytePosAndLen(pipe, copyASCIIToBytes(source, sourceIdx, sourceCharCount, pipe), sourceCharCount);
}
/**
* Add these chars as ASCII values to output pipe
* @param source char[] char source
* @param sourceIdx int start position of chars
* @param sourceCharCount int count of chars
* @param pipe Pipe target to write byte field
*/
public static > void addASCII(char[] source, int sourceIdx,
int sourceCharCount, Pipe pipe) {
addBytePosAndLen(pipe, copyASCIIToBytes(source, sourceIdx, sourceCharCount, pipe), sourceCharCount);
}
/**
* Add these chars as ASCII values to output pipe
* @param source CharSequence source chars
* @param sourceIdx start position of chars to read
* @param sourceLen length of chars to read
* @param pipe Pipe target pipe
* @return int new position after write
*/
public static > int copyASCIIToBytes(CharSequence source, int sourceIdx, final int sourceLen, Pipe pipe) {
final int p = pipe.blobRingHead.byteWorkingHeadPos.value;
if (sourceLen > 0) {
int tStart = p & pipe.blobMask;
copyASCIIToBytes2(source, sourceIdx, sourceLen, pipe, p, pipe.blobRing, tStart, 1+pipe.blobMask - tStart);
}
return p;
}
private static > void copyASCIIToBytes2(CharSequence source, int sourceIdx,
final int sourceLen, Pipe rbRingBuffer, final int p,
byte[] target, int tStart, int len1) {
if (len1>=sourceLen) {
Pipe.copyASCIIToByte(source, sourceIdx, target, tStart, sourceLen);
} else {
// done as two copies
Pipe.copyASCIIToByte(source, sourceIdx, target, tStart, len1);
Pipe.copyASCIIToByte(source, sourceIdx + len1, target, 0, sourceLen - len1);
}
rbRingBuffer.blobRingHead.byteWorkingHeadPos.value = BYTES_WRAP_MASK&(p + sourceLen);
}
/**
* Copy ascii chars as bytes into a var field in the pipe
* @param source char[] char source
* @param sourceIdx int start position to read chars
* @param sourceLen int length to copy
* @param pipe Pipe target pipe to add field
* @return int new position after write
*/
public static