package lsr.paxos; import static lsr.common.ProcessDescriptor.processDescriptor; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import lsr.common.ClientRequest; import lsr.common.RequestType; import lsr.common.SingleThreadDispatcher; import lsr.paxos.core.Paxos; import lsr.paxos.core.ProposerImpl; import lsr.paxos.replica.ClientRequestBatcher; import lsr.paxos.replica.DecideCallback; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class NewPassiveBatcher implements Batcher { /** see {@link ClientRequestBatcher#PRELONGED_BATCHING_TIME} */ public static final int PRELONGED_BATCHING_TIME = ClientRequestBatcher.PRELONGED_BATCHING_TIME; public static final int BATCH_HEADER_SIZE = 4; public static final int ONDEMAND_ACCEPT_THRESHOLD = processDescriptor.batchingLevel / 2; // // // local data // // // // batch has been requested and not passed on private volatile boolean batchRequested = false; // batch requested, not passed on and batch delay expired private boolean instantBatch = false; private ScheduledFuture<?> timeOutTaskF = null; private List<RequestType> underConstructionBatch = new ArrayList<RequestType>(); private int underConstructionSize = BATCH_HEADER_SIZE; private ConcurrentLinkedQueue<byte[]> fullBatches = new ConcurrentLinkedQueue<byte[]>(); private volatile SingleThreadDispatcher batcherThread = null; // // // other JPaxos modules // // // private final ProposerImpl proposer; private final SingleThreadDispatcher paxosDispatcher; private DecideCallback decideCallback = null; // // // code // // // public NewPassiveBatcher(Paxos paxos) { this.proposer = (ProposerImpl) paxos.getProposer(); this.paxosDispatcher = paxos.getDispatcher(); } public void setDecideCallback(DecideCallback decideCallback) { this.decideCallback = decideCallback; } public void start() { } /* * (non-Javadoc) * * @see lsr.paxos.Batcher#enqueueClientRequest(lsr.common.RequestType) */ @Override public void enqueueClientRequest(final RequestType request) { // // WARNING: called from SELECTOR thread disrectly! // SingleThreadDispatcher currBatcherThread = batcherThread; // TODO: do something about lost requests. if (currBatcherThread == null) { logger.debug("Loosing client request {} due to unprepared batcher", request); return; } currBatcherThread.execute(new Runnable() { @Override public void run() { enqueueClientRequestInternal(request); } }); } private void enqueueClientRequestInternal(RequestType request) { if (!underConstructionBatch.isEmpty()) { // request doesn't fit anymore if (request.byteSize() + underConstructionSize > processDescriptor.batchingLevel) finishBatch(); } underConstructionBatch.add(request); underConstructionSize += request.byteSize(); if (instantBatch || underConstructionSize > processDescriptor.batchingLevel) // single request is bigger than batching lvl finishBatch(); } private void finishBatch() { assert batcherThread.amIInDispatcher(); assert underConstructionSize > BATCH_HEADER_SIZE; byte[] newBatch = new byte[underConstructionSize]; ByteBuffer bb = ByteBuffer.wrap(newBatch); bb.putInt(underConstructionBatch.size()); for (RequestType req : underConstructionBatch) { req.writeTo(bb); } assert (bb.remaining() == 0); fullBatches.add(newBatch); logger.debug("Prepared batch with {} requests of size {}; instant is {}", underConstructionBatch.size(), underConstructionSize, instantBatch); underConstructionBatch.clear(); underConstructionSize = BATCH_HEADER_SIZE; if (batchRequested) { if (timeOutTaskF != null) { timeOutTaskF.cancel(false); timeOutTaskF = null; } batchRequested = false; instantBatch = false; proposer.notifyAboutNewBatch(); } } @Override public byte[] requestBatch() { byte[] batch = fullBatches.poll(); if (batch == null) { SingleThreadDispatcher currBatcherThread = batcherThread; if (currBatcherThread == null) return null; currBatcherThread.executeAndWait(new Runnable() { public void run() { requestBatchInternal(); } }); batch = fullBatches.poll(); } return batch; } protected void requestBatchInternal() { if (!batchRequested) { batchRequested = true; assert timeOutTaskF == null; timeOutTaskF = batcherThread.schedule(new Runnable() { public void run() { timedOut(); } }, processDescriptor.maxBatchDelay, TimeUnit.MILLISECONDS); } } protected void timedOut() { if (decideCallback.hasDecidedNotExecutedOverflow()) { // gtfo JPaxos, you decided too much. execute it first. logger.debug("Delaying batcher tmeout - decided and not executed overflow"); timeOutTaskF = batcherThread.schedule(new Runnable() { public void run() { timedOut(); } }, PRELONGED_BATCHING_TIME, TimeUnit.MILLISECONDS); return; } logger.debug("Batcher timeout expired."); timeOutTaskF = null; if (!underConstructionBatch.isEmpty()) { finishBatch(); } else { assert (batchRequested); instantBatch = true; } } @Override public void suspendBatcher() { assert paxosDispatcher.amIInDispatcher(); if (batcherThread == null) return; final SingleThreadDispatcher oldBatcherThread = batcherThread; batcherThread = null; oldBatcherThread.executeAndWait(new Runnable() { @Override public void run() { oldBatcherThread.shutdownNow(); logger.info("Suspend batcher"); } }); } @Override public void resumeBatcher(int nextInstanceId) { assert paxosDispatcher.amIInDispatcher(); assert batcherThread == null; logger.info("Resuming batcher."); batcherThread = new SingleThreadDispatcher("Batcher"); batcherThread.setRejectedExecutionHandler(new RejectedExecutionHandler() { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { // This batcher is respawned now and then due to Java // miserableness in scheduling, and minimum-synchronization // thread architecture makes it possible to schedule something // past shutdown. If so, warn only. if (executor.isShutdown()) { logger.debug("Batcher task scheduled post shutdown: {}", r); return; } throw new RuntimeException("" + r + " " + executor); } }); batcherThread.start(); } @Override public void instanceExecuted(int instanceId, ClientRequest[] requests) { } private final static Logger logger = LoggerFactory.getLogger(NewPassiveBatcher.class); }