com.javanut.pronghorn.pipe.util.StreamRegulator 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.util;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.javanut.pronghorn.pipe.DataInputBlobReader;
import com.javanut.pronghorn.pipe.DataOutputBlobWriter;
import com.javanut.pronghorn.pipe.Pipe;
import com.javanut.pronghorn.pipe.PipeConfig;
import com.javanut.pronghorn.pipe.RawDataSchema;
public class StreamRegulator {
private final Logger log = LoggerFactory.getLogger(StreamRegulator.class);
private final Pipe pipe;
private final DataInputBlobReader inputStreamFlyweight;
private final DataOutputBlobWriter outputStreamFlyweight;
private final long bitPerSecond;
private boolean hasOpenRead;
private boolean hasOpenWrite;
private long readStartTime;
private long writeStartTime;
private long totalBytesRead;
private long totalBytesWritten;
private static final int MSG_SIZE = RawDataSchema.FROM.fragDataSize[RawDataSchema.MSG_CHUNKEDSTREAM_1];
public StreamRegulator(long bitPerSecond, int maxWrittenChunksInFlight, int maxWrittenChunkSizeInBytes) {
PipeConfig pipeConfig = new PipeConfig(RawDataSchema.instance, maxWrittenChunksInFlight, maxWrittenChunkSizeInBytes);
this.pipe = new Pipe(pipeConfig);
this.pipe.initBuffers();
Pipe.setPublishBatchSize(pipe, 0);
Pipe.setReleaseBatchSize(pipe, maxWrittenChunksInFlight/3);
if (this.pipe.blobMask<=0) {
throw new UnsupportedOperationException("Pipe must have room to send blob data. Found size:"+ this.pipe.sizeOfBlobRing+" config: "+pipeConfig);
}
this.inputStreamFlyweight = new DataInputBlobReader(pipe);
this.outputStreamFlyweight = new DataOutputBlobWriter(pipe);
this.bitPerSecond = bitPerSecond;
//TODO: may want to add latency per chunk, per startup, or per N bytes.
}
public String toString() {
return pipe.toString();
}
public InputStream getInputStream() {
return inputStreamFlyweight;
}
public DataInput getDataInput() {
return inputStreamFlyweight;
}
public DataInputBlobReader getBlobReader() {
return inputStreamFlyweight;
}
/**
* Blocks until the desired time as passed to ensure this stream conforms to the requested bits per second.
* Once the time has passed it returns so the InputStream can read the next chunk.
*/
public final boolean hasNextChunk() {
if (!readChunk()) {
return false; //No chunk now try again
}
waitAsNeededForRead();
return true;
}
private void waitAsNeededForRead() {
long now = System.currentTimeMillis();
if (readStartTime==now) {
return;
}
long bitsRead = 8L * totalBytesRead;
long expectedTime = readStartTime+ (bitsRead*1000L/bitPerSecond);
if (expectedTime>now) {
try {
Thread.sleep(expectedTime-now);
} catch (InterruptedException e) {
shutdown("Interrupted");
}
}
}
private boolean readChunk() {
readPrep();
if (Pipe.hasContentToRead(pipe)) {
return beginNewRead();
}
return false;
}
private boolean beginNewRead() {
int msgIdx = Pipe.takeMsgIdx(pipe);
if (RawDataSchema.MSG_CHUNKEDSTREAM_1 == msgIdx) {
totalBytesRead = totalBytesRead+inputStreamFlyweight.openLowLevelAPIField();
hasOpenRead = true;
return true;
} else {
shutdown("EOF Message detected.");
}
return false;
}
private void readPrep() {
if (hasOpenRead) {
releaseOpenRead();
} else if (0 == readStartTime) {
readStartTime = System.currentTimeMillis();
}
}
private void releaseOpenRead() {
//log.trace("release block");
Pipe.confirmLowLevelRead(pipe, MSG_SIZE);
Pipe.releaseReadLock(pipe);
hasOpenRead = false;
}
public void shutdown() {
shutdown(null);
}
private void shutdown(String reason) {
if (null!=reason) {
log.warn(reason);
}
try {
inputStreamFlyweight.close();
outputStreamFlyweight.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public OutputStream getOutputStream() {
return outputStreamFlyweight;
}
public DataOutput getDataOutput() {
return outputStreamFlyweight;
}
//TODO: add support for WHY there is no room, return sleep duration?
/**
* Blocks until the desired time as passed to ensure this stream conforms to the requested bits per second.
* Once the time has passed the output stream buffer will send its data and be ready for more writes.
*/
public final boolean hasRoomForChunk() {
if (!openForWrite()) {
return false;
};
waitAsNeededForWrite();
return true;
}
private void waitAsNeededForWrite() {
long now = System.currentTimeMillis();
if (writeStartTime==now) {
return;
}
long bitsWritten = 8L * totalBytesWritten;
long expectedTime = writeStartTime+ (bitsWritten*1000L/bitPerSecond);
if (expectedTime>now) {
try {
Thread.sleep(expectedTime-now);
} catch (InterruptedException e) {
shutdown("Interrupted");
}
}
}
private boolean openForWrite() {
writePrep();
if (Pipe.hasRoomForWrite(pipe)) {
return beginNewWrite();
}
return false;
}
private boolean beginNewWrite() {
Pipe.addMsgIdx(pipe, RawDataSchema.MSG_CHUNKEDSTREAM_1);
outputStreamFlyweight.openField();
hasOpenWrite = true;
return true;
}
private void writePrep() {
if (hasOpenWrite) {
publishOpenWrite();
} else if (0 == writeStartTime) {
writeStartTime = System.currentTimeMillis();
}
}
private void publishOpenWrite() {
//log.trace("write block");
totalBytesWritten = totalBytesWritten + (outputStreamFlyweight.closeLowLevelField());
Pipe.confirmLowLevelWrite(pipe, Pipe.sizeOf(pipe, RawDataSchema.MSG_CHUNKEDSTREAM_1));
Pipe.publishWrites(pipe);
hasOpenWrite = false;
}
public long getBytesWritten() {
return totalBytesWritten;
}
public long getBytesRead() {
return totalBytesWritten;
}
public DataOutputBlobWriter getBlobWriter() {
return outputStreamFlyweight;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy