package uk.ac.imperial.lsds.seepworker.core.input;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import uk.ac.imperial.lsds.seep.api.DataReference;
import uk.ac.imperial.lsds.seep.api.data.TupleInfo;
import uk.ac.imperial.lsds.seep.core.IBuffer;
import uk.ac.imperial.lsds.seep.core.InputAdapter;
import uk.ac.imperial.lsds.seepworker.WorkerConfig;
public class InputBuffer implements IBuffer {
private DataReference dRef;
private ByteBuffer header = ByteBuffer.allocate(TupleInfo.PER_BATCH_OVERHEAD_SIZE);
private ByteBuffer payload = null;
private int nTuples = 0;
private BlockingQueue<byte[]> queue;
private int queueSize;
private InputBuffer(WorkerConfig wc, DataReference dr) {
this.queueSize = wc.getInt(WorkerConfig.SIMPLE_INPUT_QUEUE_LENGTH);
this.queue = new ArrayBlockingQueue<>(queueSize);
this.dRef = dr;
}
public static InputBuffer makeInputBufferFor(WorkerConfig wc, DataReference dr) {
return new InputBuffer(wc, dr);
}
@Override
public DataReference getDataReference() {
return dRef;
}
@Override
public int readFrom(ReadableByteChannel channel) {
int totalTuplesRead = 0;
if(header.remaining() > 0) {
this.read(channel, header);
}
if(payload == null && !header.hasRemaining()) {
header.flip();
byte control = header.get();
nTuples = header.getInt();
int payloadSize = header.getInt(); // payload size
payload = ByteBuffer.allocate(payloadSize);
}
if(payload != null) {
this.read(channel, payload);
if(!payload.hasRemaining()) {
totalTuplesRead = this.forwardTuples(payload, nTuples);
payload = null;
header.clear();
nTuples = 0;
}
}
return totalTuplesRead;
}
private int read(ReadableByteChannel src, ByteBuffer dst){
try {
return src.read(dst);
}
catch (IOException e) {
e.printStackTrace();
}
return -1;
}
private int forwardTuples(ByteBuffer buf, int numTuples) {
int totalTuplesForwarded = 0;
int tupleSize = 0;
buf.flip(); // Prepare buffer to read
for(int i = 0; i < numTuples; i++){
tupleSize = buf.getInt();
byte[] completedRead = new byte[tupleSize];
buf.get(completedRead, 0, tupleSize);
this.pushData(completedRead);
totalTuplesForwarded++;
}
buf.clear();
return totalTuplesForwarded;
}
@Override
public void pushData(byte[] data) {
try {
queue.put(data);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public byte[] read(int timeout) {
try {
return queue.poll(timeout, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
// @Deprecated
// public void readFrom(ReadableByteChannel channel, InputAdapter ia) {
//
// if(header.remaining() > 0){
// this.read(channel, header);
// }
//
// if(payload == null && !header.hasRemaining()){
// header.flip();
// byte control = header.get();
// nTuples = header.getInt();
// int payloadSize = header.getInt(); // payload size
// payload = ByteBuffer.allocate(payloadSize);
// }
//
// if(payload != null){
// this.read(channel, payload);
// if(!payload.hasRemaining()){
// this.forwardTuples(payload, nTuples, ia);
// payload = null;
// header.clear();
// nTuples = 0;
// }
// }
// }
/**
* TODO: refactor according to the new model (above) : NetworkBarrier
*/
public InputBuffer(int size){
buffer = ByteBuffer.allocate(size);
completedReads = new ArrayDeque<>();
}
// Used only for barrier, smells like refactoring...
private ByteBuffer buffer;
private Deque<byte[]> completedReads;
public boolean hasCompletedReads(){
return completedReads.size() > 0;
}
public byte[] __read(){
return completedReads.poll();
}
public boolean readToInternalBuffer(ReadableByteChannel channel, InputAdapter ia){
boolean dataRemainingInBuffer = true;
int readBytes = 0;
try {
readBytes = ((SocketChannel)channel).read(buffer);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
int initialLimit = buffer.position();
int fromPosition = 0;
while(dataRemainingInBuffer){
if(canReadFullBatch(fromPosition, initialLimit)){
buffer.limit(initialLimit);
buffer.position(fromPosition);
byte control = buffer.get();
int numTuples = buffer.getInt();
int batchSize = buffer.getInt();
for(int i = 0; i < numTuples; i++){
int tupleSize = buffer.getInt();
byte[] completedRead = new byte[tupleSize];
buffer.get(completedRead, 0, tupleSize);
completedReads.add(completedRead);
}
fromPosition = buffer.position(); // Update position for next iteration
}
else{
if(buffer.hasRemaining()){
buffer.compact(); // make space to complete chunked read
return false;
}
else{
dataRemainingInBuffer = false;
buffer.clear();
return true; // Fully read buffer
}
}
}
return false;
}
private boolean canReadFullBatch(int fromPosition, int limit){
// Check whether we can read a complete batch
int initialPosition = buffer.position();
int initialLimit = buffer.limit();
buffer.position(fromPosition);
buffer.limit(limit);
int remaining = buffer.remaining();
if(remaining < TupleInfo.PER_BATCH_OVERHEAD_SIZE){
// Reset buffer back to initial status and wait for more data to arrive
buffer.limit(initialLimit);
buffer.position(initialPosition);
return false;
}
else{
buffer.position(fromPosition + TupleInfo.BATCH_SIZE_OFFSET);
int batchSize = buffer.getInt();
buffer.limit(initialLimit);
buffer.position(initialPosition);
if(remaining < batchSize){
return false;
}
return true;
}
}
}