package lsr.paxos.replica; import static lsr.common.ProcessDescriptor.processDescriptor; import java.util.ArrayList; import java.util.BitSet; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import lsr.common.ClientRequest; import lsr.common.SingleThreadDispatcher; import lsr.paxos.core.Paxos; import lsr.paxos.messages.AskForClientBatch; import lsr.paxos.messages.ForwardClientBatch; import lsr.paxos.messages.Message; import lsr.paxos.messages.MessageType; import lsr.paxos.network.MessageHandler; import lsr.paxos.network.Network; import lsr.paxos.storage.ClientBatchStore; import org.slf4j.Logger; import org.slf4j.LoggerFactory; final public class ClientBatchManager { private final Network network; private final Paxos paxos; private final Replica replica; private final int localId; private final SingleThreadDispatcher dispatcher = new SingleThreadDispatcher( "CliBatchManager"); private final ClientBatchStore batchStore; /** Maps missing batch ID to task(s) for retrieving it */ private final HashMap<ClientBatchID, List<FwdBatchRetransmitter>> missingBatches = new HashMap<ClientBatchID, List<FwdBatchRetransmitter>>(); private final HashMap<FwdBatchRetransmitter, ScheduledFuture<?>> taskToFuture = new HashMap<ClientBatchManager.FwdBatchRetransmitter, ScheduledFuture<?>>(); public ClientBatchManager(Paxos paxos, Replica replica) { this.paxos = paxos; network = paxos.getNetwork(); this.replica = replica; localId = processDescriptor.localId; batchStore = ClientBatchStore.instance; new InternalMessageHandler(); } public void start() { dispatcher.start(); } private class InternalMessageHandler implements MessageHandler { public InternalMessageHandler() { Network.addMessageListener(MessageType.ForwardedClientBatch, this); Network.addMessageListener(MessageType.AskForClientBatch, this); } public void onMessageReceived(final Message msg, final int sender) { dispatcher.submit(new Runnable() { public void run() { if (msg instanceof ForwardClientBatch) { onForwardClientBatch((ForwardClientBatch) msg, sender); } else if (msg instanceof AskForClientBatch) { onAskForClientBatch((AskForClientBatch) msg, sender); } else { assert false : "Unknown message type: " + msg; } } }); } public void onMessageSent(Message message, BitSet destinations) { // Ignore } } private void onAskForClientBatch(AskForClientBatch msg, int sender) { logger.debug("Received {} from {}", msg, sender); for (ClientBatchID cbId : msg.getNeededBatches()) { ClientRequest[] batchValue = batchStore.getBatch(cbId); if (batchValue != null) { logger.debug("Forwarding {} to {}", cbId, sender); network.sendMessage(new ForwardClientBatch(cbId, batchValue), sender); } else { logger.info("Could not deliver requestd batch contents for {}", cbId); } } } private void checkIfInDispatcher() { assert dispatcher.amIInDispatcher() : "Not in ClientBatchManager dispatcher. " + Thread.currentThread().getName(); } /** * Received a forwarded request. */ private void onForwardClientBatch(ForwardClientBatch fReq, int sender) { checkIfInDispatcher(); List<FwdBatchRetransmitter> tasks = missingBatches.remove(fReq.rid); if (tasks != null) { batchStore.setBatch(fReq); for (FwdBatchRetransmitter task : tasks) { task.fetched(fReq.rid); } return; } if (!usefull(fReq)) return; batchStore.setBatch(fReq); tryPropose(fReq.rid); } /** Returns true iff either required or contain undecided client requests */ private boolean usefull(ForwardClientBatch fReq) { return batchStore.isAnyInstanceWaiting(fReq.rid) || replica.hasUnexecutedRequests(fReq.requests); } private void tryPropose(ClientBatchID cbId) { if (paxos.isLeader()) paxos.enqueueRequest(cbId); } /** Transmits a batch to the other replicas */ public void dispatchForwardNewBatch(final ClientBatchID bid, final ClientRequest[] batches) { dispatcher.submit(new Runnable() { @Override public void run() { forwardNewBatch(bid, batches); } }); } private void forwardNewBatch(ClientBatchID bid, ClientRequest[] batches) { checkIfInDispatcher(); assert processDescriptor.indirectConsensus; // The object that will be sent. ForwardClientBatch fReqMsg = new ForwardClientBatch(bid, batches); logger.debug("Forwarding batch: {}", fReqMsg); network.sendToOthers(fReqMsg); // Local delivery onForwardClientBatch(fReqMsg, localId); } public static interface Hook { void hook(); } public class FwdBatchRetransmitter implements Runnable { private final List<ClientBatchID> missing; private int nextReplicaToAsk; private Hook hook; protected FwdBatchRetransmitter(List<ClientBatchID> missing, Hook hook) { this.missing = missing; this.hook = hook; nextReplicaToAsk = processDescriptor.nextReplica(localId); } public void fetched(ClientBatchID cbId) { missing.remove(cbId); if (missing.isEmpty()) finished(); } public void run() { checkIfInDispatcher(); List<ClientBatchID> mine = mine(); if (mine.isEmpty()) return; network.sendMessage(new AskForClientBatch(mine), nextReplicaToAsk); nextReplicaToAsk = processDescriptor.nextReplica(nextReplicaToAsk); } private List<ClientBatchID> mine() { List<ClientBatchID> l = new ArrayList<ClientBatchID>(); for (ClientBatchID m : missing) { assert missingBatches.get(m) != null : missingBatches; if (missingBatches.get(m).get(0) == this) { l.add(m); } } return l; } private void finished() { ScheduledFuture<?> sf = taskToFuture.remove(this); if (sf != null) sf.cancel(false); dispatcher.remove(this); dispatcher.purge(); if (hook != null) hook.hook(); } } /** * Fetches batches and calls hook afterwards */ public FwdBatchRetransmitter fetchMissingBatches(final Collection<ClientBatchID> cbids, final Hook hook, final boolean instant) { final FwdBatchRetransmitter[] ans = new FwdBatchRetransmitter[1]; dispatcher.executeAndWait(new Runnable() { @Override public void run() { List<ClientBatchID> missing = new ArrayList<ClientBatchID>(); for (ClientBatchID cbId : cbids) { if (cbId.isNop()) continue; if (batchStore.getBatch(cbId) == null) { missing.add(cbId); } } if (missing.isEmpty()) { if (hook != null) hook.hook(); } FwdBatchRetransmitter fbr = new FwdBatchRetransmitter(missing, hook); for (ClientBatchID cbId : missing) { if (missingBatches.get(cbId) == null) missingBatches.put(cbId, new ArrayList<FwdBatchRetransmitter>()); missingBatches.get(cbId).add(fbr); } ScheduledFuture<?> sf = dispatcher.scheduleAtFixedRate(fbr, instant ? 0 : processDescriptor.retransmitTimeout, processDescriptor.retransmitTimeout / processDescriptor.numReplicas, TimeUnit.MILLISECONDS); taskToFuture.put(fbr, sf); synchronized (ans) { ans[0] = fbr; } } }); synchronized (ans) { return ans[0]; } } // TODO: (JK) check if the method below is longer needed /** Clears all tasks hanging upon the provided batches */ public void removeBatches(final Collection<ClientBatchID> cbids) { dispatcher.execute(new Runnable() { public void run() { for (ClientBatchID cbid : cbids) { List<FwdBatchRetransmitter> fbrs = missingBatches.remove(cbid); if (fbrs != null) for (FwdBatchRetransmitter fbr : fbrs) { ScheduledFuture<?> sf = taskToFuture.remove(fbr); if (sf != null) { sf.cancel(false); dispatcher.remove(fbr); } } } dispatcher.purge(); } }); } public void removeTask(final FwdBatchRetransmitter fbr) { dispatcher.executeAndWait(new Runnable() { public void run() { ScheduledFuture<?> sf = taskToFuture.remove(fbr); if (sf == null) return; sf.cancel(true); dispatcher.remove(fbr); dispatcher.purge(); } }); } static final Logger logger = LoggerFactory.getLogger(ClientBatchManager.class); }