package lsr.paxos.replica;
import static lsr.common.ProcessDescriptor.processDescriptor;
import java.util.ArrayList;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
import lsr.common.ClientRequest;
import lsr.common.MovingAverage;
import lsr.paxos.storage.Storage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This thread builds the batches with the requests received from the client and
* forwards them to the leader. The selectors place the requests in a queue
* managed owned by this class. The ForwardingThread reads requests from this
* queue and groups them into batches.
*
* There is some contention between the Selector threads and the Forwarding
* thread in the shared queue, but it should be acceptable. For 4 selectors, in
* a 180s run:
*
* <pre>
* (# blocked times, blocking time) (#waits, waiting time)
* Selector-X (67388 3365) (194668 11240)
* ForwardingBatcher (95081 3810) (1210222 96749)
* </pre>
*
* @author Nuno Santos (LSR)
*/
public class ClientRequestBatcher implements Runnable {
/**
* If the process executes slower than decides, the batcher waits longer in
* case the execution queue is large.
*
* This time describes how often the queue size will be checked after normal
* timeout expires, but the queue in decide callback has a lot of unexecuted
* requests.
*/
public static final int PRELONGED_BATCHING_TIME = 50;
private static int nextBatchId = 1;
/*
* Selector threads enqueue requests in this queue. The Batcher thread takes
* requests from here to prepare batches.
*/
private final ArrayBlockingQueue<ClientRequest> cBatcherQueue = new ArrayBlockingQueue<ClientRequest>(
128);
/*
* Stores the requests that will make the next batch. We use two queues to
* minimize contention between the Selector threads and the Batcher thread,
* since they only have to contend for the first queue, which is accessed
* very briefly by either thread.
*/
private final ArrayList<ClientRequest> batch = new ArrayList<ClientRequest>(16);
// Total size of the requests stored in the batch array.
private int sizeInBytes = 0;
private final Thread batcherThread;
private final ClientBatchManager batchManager;
private final DecideCallback decideCallback;
private final ClientRequestForwarder requestForwarder;
public ClientRequestBatcher(ClientBatchManager batchManager, DecideCallback decideCallback) {
assert processDescriptor.indirectConsensus;
this.batchManager = batchManager;
this.requestForwarder = null;
this.decideCallback = decideCallback;
this.batcherThread = new Thread(this, "CliReqBatcher");
}
public ClientRequestBatcher(ClientRequestForwarder requestForwarder,
DecideCallback decideCallback) {
assert !processDescriptor.indirectConsensus;
this.batchManager = null;
this.requestForwarder = requestForwarder;
this.decideCallback = decideCallback;
this.batcherThread = new Thread(this, "CliReqBatcher");
}
public void start() {
batcherThread.start();
}
public void enqueueRequest(ClientRequest fReqMsg) throws InterruptedException {
cBatcherQueue.put(fReqMsg);
}
protected static int uniqueRunId = -1;
public static void generateUniqueRunId(Storage storage) {
int base = 0;
switch (processDescriptor.crashModel) {
case FullSS:
base = (int) storage.getEpoch()[0];
break;
case ViewSS:
base = storage.getView();
break;
case EpochSS:
base = (int) storage.getEpoch()[processDescriptor.localId];
break;
case CrashStop:
break;
default:
throw new RuntimeException("Unknown crash model for ViewEpoch idgen.");
}
uniqueRunId = base * processDescriptor.numReplicas + processDescriptor.localId;
}
@Override
public void run() {
long batchStart = 0;
MovingAverage averageRequestSize = new MovingAverage(0.2, 0);
ClientRequest overflow = null;
while (true) {
ClientRequest request;
try {
if (overflow == null) {
request = cBatcherQueue.take();
averageRequestSize.add(request.byteSize());
batchStart = System.currentTimeMillis();
} else {
request = overflow;
overflow = null;
}
} catch (InterruptedException e) {
throw new RuntimeException("Thread interrupted. Quitting.");
}
batch.add(request);
sizeInBytes = request.byteSize();
while (sizeInBytes < processDescriptor.forwardBatchMaxSize) {
request = null;
try {
int timeToExpire = (int) (batchStart + processDescriptor.forwardBatchMaxDelay - System.currentTimeMillis());
request = cBatcherQueue.poll(timeToExpire, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
throw new RuntimeException("Thread interrupted. Quitting.");
}
if (request == null) {
// Timeout expired
logger.trace("Batch timed out with {}/{}", sizeInBytes,
processDescriptor.forwardBatchMaxSize);
// if the service has much to do, one can wait for client
// requests longer
if (decideCallback.hasDecidedNotExecutedOverflow()) {
batchStart = System.currentTimeMillis();
if (processDescriptor.forwardBatchMaxDelay < PRELONGED_BATCHING_TIME) {
batchStart += PRELONGED_BATCHING_TIME -
processDescriptor.forwardBatchMaxDelay;
}
logger.info("Prelonging batching in ClientRequestBatcher");
continue;
} else {
break;
}
}
averageRequestSize.add(request.byteSize());
if (sizeInBytes + request.byteSize() > processDescriptor.forwardBatchMaxSize) {
// request won't fit.
batchStart = System.currentTimeMillis();
overflow = request;
break;
}
batch.add(request);
sizeInBytes += request.byteSize();
if (sizeInBytes + (averageRequestSize.get() / 2) > processDescriptor.forwardBatchMaxSize) {
// small chance to fit the next request.
if (cBatcherQueue.isEmpty()) {
if (logger.isTraceEnabled()) {
logger.trace(
"Predicting that next request won't fit. Left with {} bytes, estimated request size: {}",
(sizeInBytes - processDescriptor.forwardBatchMaxSize),
averageRequestSize.get());
}
break;
}
}
}
sendBatch();
}
}
private void sendBatch() {
assert Thread.currentThread() == batcherThread;
assert sizeInBytes > 0 : "Trying to send an empty batch.";
assert uniqueRunId != -1;
final ClientBatchID bid = new ClientBatchID(uniqueRunId, nextBatchId++);
// Transform the ArrayList into an array with the exact size.
final ClientRequest[] batches = batch.toArray(new ClientRequest[batch.size()]);
if (processDescriptor.indirectConsensus)
batchManager.dispatchForwardNewBatch(bid, batches);
else
requestForwarder.forward(batches);
batch.clear();
sizeInBytes = 0;
}
static final Logger logger = LoggerFactory.getLogger(ClientRequestBatcher.class);
}