package lsr.paxos.core;
import static lsr.common.ProcessDescriptor.processDescriptor;
import java.util.Deque;
import lsr.paxos.UnBatcher;
import lsr.paxos.messages.Accept;
import lsr.paxos.messages.Prepare;
import lsr.paxos.messages.PrepareOK;
import lsr.paxos.messages.Propose;
import lsr.paxos.network.Network;
import lsr.paxos.replica.ClientBatchID;
import lsr.paxos.replica.ClientBatchManager;
import lsr.paxos.replica.ClientBatchManager.FwdBatchRetransmitter;
import lsr.paxos.storage.ClientBatchStore;
import lsr.paxos.storage.ConsensusInstance;
import lsr.paxos.storage.ConsensusInstance.LogEntryState;
import lsr.paxos.storage.Log;
import lsr.paxos.storage.Storage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Represents part of paxos which is responsible for responding on the
* <code>Prepare</code> message, and also sending <code>Accept</code> after
* receiving proper <code>Propose</code>.
*/
class Acceptor {
private final Paxos paxos;
private final Storage storage;
private final Network network;
/**
* Initializes new instance of <code>Acceptor</code>.
*
* @param paxos - the paxos the acceptor belong to
* @param storage - data associated with the paxos
* @param network - used to send responses
*
*/
public Acceptor(Paxos paxos, Storage storage, Network network) {
this.paxos = paxos;
this.storage = storage;
this.network = network;
}
/**
* Promises not to accept a proposal numbered less than message view. Sends
* the proposal with the highest number less than message view that it has
* accepted if any. If message view equals current view, then it may be a
* retransmission or out-of-order delivery. If the process already accepted
* this proposal, then the proposer doesn't need anymore the prepareOK
* message. Otherwise it might need the message, so resent it.
*
* @param msg received prepare message
* @see Prepare
*/
public void onPrepare(Prepare msg, int sender) {
assert paxos.getDispatcher().amIInDispatcher() : "Thread should not be here: " +
Thread.currentThread();
if (!paxos.isActive())
return;
// TODO: JK: When can we skip responding to a prepare message?
// Is detecting stale prepare messages it worth it?
logger.info("{} From {}", msg, sender);
Log log = storage.getLog();
if (msg.getFirstUncommitted() < log.getLowestAvailableId()) {
// We're MUCH MORE up-to-date than the replica that sent Prepare
paxos.startProposer();
return;
}
ConsensusInstance[] v = new ConsensusInstance[Math.max(
log.getNextId() - msg.getFirstUncommitted(), 0)];
for (int i = msg.getFirstUncommitted(); i < log.getNextId(); i++) {
v[i - msg.getFirstUncommitted()] = log.getInstance(i);
}
PrepareOK m = new PrepareOK(msg.getView(), v, storage.getEpoch());
logger.info("Sending {}", m);
network.sendMessage(m, sender);
}
/**
* Accepts proposals higher or equal than the current view.
*
* @param message - received propose message
* @param sender - the id of replica that send the message
*/
public void onPropose(final Propose message, final int sender) {
assert message.getView() == storage.getView() : "Msg.view: " + message.getView() +
", view: " + storage.getView();
assert paxos.getDispatcher().amIInDispatcher();
ConsensusInstance instance = storage.getLog().getInstance(message.getInstanceId());
// The propose is so old, that it's log has already been erased
if (instance == null) {
logger.debug("Ignoring old message: {}", message);
return;
}
if (logger.isDebugEnabled()) {
logger.debug("onPropose. View:instance: {}:{}", message.getView(),
message.getInstanceId());
}
Deque<ClientBatchID> cbids = null;
if (processDescriptor.indirectConsensus) {
cbids = UnBatcher.unpackCBID(message.getValue());
// leader must have the values
if (!paxos.isLeader()) {
// as follower, we may be missing the real value. If so, need to
// wait for it.
if (!ClientBatchStore.instance.hasAllBatches(cbids)) {
logger.debug("Missing batch values for instance {}. Delaying onPropose.",
instance.getId());
FwdBatchRetransmitter fbr = ClientBatchStore.instance.getClientBatchManager().fetchMissingBatches(
cbids,
new ClientBatchManager.Hook() {
@Override
public void hook() {
paxos.getDispatcher().execute(new Runnable() {
public void run() {
onPropose(message, sender);
}
});
}
}, false);
instance.setFwdBatchForwarder(fbr);
return;
}
}
}
// In FullSS, updating state leads to setting new value if needed, which
// syncs to disk
instance.updateStateFromKnown(message.getView(), message.getValue());
if (processDescriptor.indirectConsensus) {
// prevent multiple unpacking
instance.setClientBatchIds(cbids);
}
assert instance.getValue() != null;
// leader will not send the accept message;
if (!paxos.isLeader()) {
if (storage.getFirstUncommitted() + (processDescriptor.windowSize * 3) < message.getInstanceId()) {
// the instance is so new that we must be out of date.
paxos.getCatchup().forceCatchup();
}
if (paxos.isActive())
network.sendToOthers(new Accept(message));
}
// we could have decided the instance earlier
if (instance.getState() == LogEntryState.DECIDED) {
logger.trace("Instance already decided: {}", message.getInstanceId());
return;
}
// The local process accepts immediately the proposal
instance.getAccepts().set(processDescriptor.localId);
// The propose message works as an implicit accept from the leader.
instance.getAccepts().set(sender);
// Check if we can decide (n<=3 or if some accepts overtook propose)
if (instance.isMajority()) {
paxos.decide(instance.getId());
}
}
private final static Logger logger = LoggerFactory.getLogger(Acceptor.class);
}