package lsr.paxos.replica; import static lsr.common.ProcessDescriptor.processDescriptor; import java.util.ArrayList; import java.util.Arrays; import java.util.Deque; import java.util.NavigableMap; import java.util.TreeMap; import lsr.common.ClientRequest; import lsr.common.MovingAverage; import lsr.common.SingleThreadDispatcher; import lsr.paxos.UnBatcher; import lsr.paxos.storage.ClientBatchStore; import lsr.paxos.storage.ConsensusInstance; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class DecideCallbackImpl implements DecideCallback { private final Replica replica; private final SingleThreadDispatcher replicaDispatcher; /** * Temporary storage for the instances that finished and are not yet * executed. * * Warning: multi-thread access */ private final NavigableMap<Integer, ConsensusInstance> decidedWaitingExecution = new TreeMap<Integer, ConsensusInstance>(); /** Next instance that will be executed on the replica. Same as in replica */ private int executeUB; /** Used to predict how much time a single instance takes */ private MovingAverage averageInstanceExecTime = new MovingAverage(0.4, 0); /** * If predicted time is larger than this threshold, batcher is given more * time to collect requests */ private static final double OVERFLOW_THRESHOLD_MS = 250; public DecideCallbackImpl(Replica replica, int executeUB) { this.replica = replica; this.executeUB = executeUB; replicaDispatcher = replica.getReplicaDispatcher(); } @Override public void onRequestOrdered(final int instance, final ConsensusInstance ci) { synchronized (decidedWaitingExecution) { decidedWaitingExecution.put(instance, ci); } replicaDispatcher.submit(new Runnable() { @Override public void run() { executeRequests(); } }); Thread.yield(); } /** Returns how many instances is the service behind the Paxos protocol */ public int decidedButNotExecutedCount() { synchronized (decidedWaitingExecution) { return decidedWaitingExecution.size(); } } private void executeRequests() { replicaDispatcher.checkInDispatcher(); if (decidedWaitingExecution.size() > 100) { // !!FIXME!! (JK) inform the proposer to inhibit proposing } logger.trace("Executing requests..."); while (true) { ConsensusInstance ci; synchronized (decidedWaitingExecution) { ci = decidedWaitingExecution.get(executeUB); } if (ci == null) { logger.debug("Cannot continue execution. Next instance not decided: {}", executeUB); return; } ClientRequest[] requests; if (processDescriptor.indirectConsensus) { logger.info("Executing instance: {}", executeUB); Deque<ClientBatchID> batch = ci.getClientBatchIds(); if (batch.size() == 1 && batch.getFirst().isNop()) { replica.executeNopInstance(executeUB); requests = new ClientRequest[0]; } else { long start = System.currentTimeMillis(); ArrayList<ClientRequest> requestsList = new ArrayList<ClientRequest>(); for (ClientBatchID bId : batch) { assert !bId.isNop(); ClientRequest[] requestsFragment = ClientBatchStore.instance.getBatch(bId); logger.debug("Executing batch: {}", bId); replica.executeClientBatchAndWait(executeUB, requestsFragment); requestsList.addAll(Arrays.asList(requestsFragment)); } requests = (ClientRequest[]) requestsList.toArray(); averageInstanceExecTime.add(System.currentTimeMillis() - start); } } else { requests = UnBatcher.unpackCR(ci.getValue()); if (logger.isDebugEnabled(processDescriptor.logMark_OldBenchmark)) { logger.info(processDescriptor.logMark_OldBenchmark, "Executing instance: {} {}", executeUB, Arrays.toString(requests)); } else { logger.info("Executing instance: {}", executeUB); } long start = System.currentTimeMillis(); replica.executeClientBatchAndWait(executeUB, requests); averageInstanceExecTime.add(System.currentTimeMillis() - start); } // Done with all the client batches in this instance replica.instanceExecuted(executeUB, requests); synchronized (decidedWaitingExecution) { decidedWaitingExecution.remove(executeUB); } executeUB++; } } public void atRestoringStateFromSnapshot(final int nextInstanceId) { replicaDispatcher.checkInDispatcher(); executeUB = nextInstanceId; replicaDispatcher.checkInDispatcher(); synchronized (decidedWaitingExecution) { if (!decidedWaitingExecution.isEmpty()) { if (decidedWaitingExecution.lastKey() < nextInstanceId) { decidedWaitingExecution.clear(); } else { while (decidedWaitingExecution.firstKey() < nextInstanceId) { decidedWaitingExecution.pollFirstEntry(); } } } } } @Override public boolean hasDecidedNotExecutedOverflow() { double predictedTime = averageInstanceExecTime.get() * decidedWaitingExecution.size(); return predictedTime >= OVERFLOW_THRESHOLD_MS; } static final Logger logger = LoggerFactory.getLogger(DecideCallbackImpl.class); }