package lsr.paxos.core; import static lsr.common.ProcessDescriptor.processDescriptor; import lsr.paxos.messages.Accept; import lsr.paxos.storage.ClientBatchStore; import lsr.paxos.storage.ConsensusInstance; import lsr.paxos.storage.ConsensusInstance.LogEntryState; import lsr.paxos.storage.Storage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Represents the part of <code>Paxos</code> which is responsible for receiving * <code>Accept</code>. When majority of process send this message it notifies * that the value is decided. */ class Learner { private final Paxos paxos; private final Proposer proposer; private final Storage storage; /** * Initializes new instance of <code>Learner</code>. * * @param paxos - the paxos the learner belong to * @param proposer - the proposer * @param storage - data associated with the paxos */ public Learner(Paxos paxos, Storage storage) { this.paxos = paxos; this.proposer = paxos.getProposer(); this.storage = storage; } /** * Decides requests from which majority of accepts was received. * * @param message - received accept message from sender * @param sender - the id of replica that send the message * @see Accept */ public void onAccept(Accept message, int sender) { assert message.getView() == storage.getView() : "Msg.view: " + message.getView() + ", view: " + storage.getView(); assert paxos.getDispatcher().amIInDispatcher() : "Thread should not be here: " + Thread.currentThread(); final ConsensusInstance instance = storage.getLog().getInstance(message.getInstanceId()); logger.trace("Learner received {}", message); // too old instance or already decided if (instance == null) { logger.info("Discarding old accept from {}:{}", sender, message); return; } if (instance.getState() == LogEntryState.DECIDED) { if (logger.isDebugEnabled()) logger.debug("Ignoring Accept. Instance already decided: {}", message.getInstanceId()); return; } if (instance.getView() == -1) { assert instance.getAccepts().isEmpty() : "First message for instance but accepts not empty: " + instance; // This is the first message received for this instance. Set the // view. instance.setView(message.getView()); } else if (message.getView() > instance.getView()) { // Reset the instance, the value and the accepts received // during the previous view aren't valid on the new view logger.debug("Accept for higher view received. Rcvd: {}, instance: {}", message, instance); instance.reset(); instance.setView(message.getView()); } else { // check correctness of received accept assert message.getView() == instance.getView(); } instance.getAccepts().set(sender); // received ACCEPT before PROPOSE if (instance.getValue() == null) { logger.debug("Out of order. Received ACCEPT before PROPOSE. Instance: {}", instance); } if (paxos.isLeader()) { proposer.stopPropose(instance.getId(), sender); } if (instance.isMajority()) { if (instance.getValue() == null) { logger.debug("Majority but no value. Delaying deciding. Instance: {}", instance.getId()); } else { assert !processDescriptor.indirectConsensus || ClientBatchStore.instance.hasAllBatches(instance.getClientBatchIds()); paxos.decide(instance.getId()); } } else { logger.trace("Not enough accepts for {} yet, got {}", instance.getId(), instance.getAccepts()); } } private final static Logger logger = LoggerFactory.getLogger(Learner.class); }