func (this *Paxos) doUpdateLearner(change *thispb.LearnerChange) error { if this.chosenValue != nil { return nil } if !this.wal.IsRecovering() { walRecord := thispb.WALRecord{} walRecord.LearnerChange = change _, errQueue := wal.QueueChangeProto(this.wal, this.uid, &walRecord) if errQueue != nil { this.Errorf("could not write learner change record: %v", errQueue) return errQueue } } if change.ChosenValue != nil { this.chosenValue = change.GetChosenValue() this.Infof("consensus result learned from proposer is %s", this.chosenValue) if this.watch != nil { this.watch.ConsensusUpdate(this.uid, -1, this.chosenValue) } return nil } acceptor := change.GetVotedAcceptor() for index := range change.VotedBallotList { ballot := change.VotedBallotList[index] value := change.VotedValueList[index] this.ballotValueMap[ballot] = value acceptorSet, found := this.ballotAcceptorsMap[ballot] if !found { acceptorSet = make(map[string]struct{}) this.ballotAcceptorsMap[ballot] = acceptorSet } acceptorSet[acceptor] = struct{}{} if len(acceptorSet) >= this.MajoritySize() { this.chosenValue = value this.Infof("consensus result learned through votes is %s", value) if this.watch != nil { this.watch.ConsensusUpdate(this.uid, -1, this.chosenValue) } } } return nil }
// LearnRPC handles ClassicPaxos.Learn rpc. func (this *Paxos) LearnRPC(header *msgpb.Header, request *thispb.LearnRequest) (status error) { if !this.IsLearner() { this.Errorf("this paxos instance is not a learner; rejecting %s", header) return errs.ErrInvalid } lock, errLock := this.ctlr.TimedLock(msg.RequestTimeout(header), "learner") if errLock != nil { return errLock } defer lock.Unlock() acceptor := header.GetMessengerId() if this.chosenValue == nil { change := thispb.LearnerChange{} change.VotedBallotList = request.VotedBallotList change.VotedValueList = request.VotedValueList change.VotedAcceptor = proto.String(acceptor) if err := this.doUpdateLearner(&change); err != nil { this.Errorf("could not update learner state: %v", err) return err } } response := thispb.LearnResponse{} if this.chosenValue != nil { response.KnowsChosenValue = proto.Bool(true) } message := thispb.PaxosMessage{} message.LearnResponse = &response errSend := msg.SendResponseProto(this.msn, header, &message) if errSend != nil { this.Errorf("could not respond to learn request from %s: %v", acceptor, errSend) return errSend } return nil }
// ProposeRPC handles ClassicPaxos.Propose rpc. func (this *Paxos) ProposeRPC(header *msgpb.Header, request *thispb.ProposeRequest) (status error) { if !this.IsProposer() { this.Errorf("this paxos instance is not a proposer; rejecting %s", header) return errs.ErrInvalid } // OPTIMIZATION If this object is also a learner and already knows the // consensus result, we don't need to perform expensive proposal. if this.IsLearner() { lock := this.ctlr.ReadLock("learner") defer lock.Unlock() if this.chosenValue != nil { response := thispb.ProposeResponse{} response.ChosenValue = this.chosenValue message := thispb.PaxosMessage{} message.ProposeResponse = &response errSend := msg.SendResponseProto(this.msn, header, &message) if errSend != nil { this.Errorf("could not send known chosen value as the propose "+ "response: %v", errSend) return errSend } return nil } lock.Unlock() } var chosen []byte proposal := request.GetProposedValue() for ii := 0; chosen == nil && msg.RequestTimeout(header) > 0; ii++ { if ii > 0 { time.Sleep(this.opts.ProposeRetryInterval) } // Get the next proposal ballot number. ballot, errNext := this.GetNextProposalBallot(msg.RequestTimeout(header)) if errNext != nil { this.Errorf("could not select higher ballot: %v", errNext) return errNext } this.Infof("using ballot number %d for the proposal", ballot) lock := this.ctlr.ReadLock("config") phase1AcceptorList := this.getPhase1AcceptorList(ballot) lock.Unlock() // Collect phase1 promises from majority number of acceptors. votedValue, acceptorList, errPhase1 := this.doPhase1(header, ballot, phase1AcceptorList) if errPhase1 != nil { this.Warningf("could not complete paxos phase1: %v", errPhase1) continue } // If a value was already voted, it may have been chosen, so propose it // instead. value := proposal if votedValue != nil { value = votedValue } // Collect phase2 votes from majority number of acceptors. errPhase2 := this.doPhase2(header, ballot, value, acceptorList) if errPhase2 != nil { this.Warningf("could not complete paxos phase2: %v", errPhase2) continue } // A value is chosen, break out of the loop. chosen = value break } if chosen == nil { this.Errorf("could not propose value %s", proposal) return errs.ErrRetry } // If local node is a learner, update him with the consensus result directly. defer func() { if this.IsLearner() { lock, errLock := this.ctlr.Lock("learner") if errLock != nil { return } defer lock.Unlock() change := thispb.LearnerChange{} change.ChosenValue = chosen if err := this.doUpdateLearner(&change); err != nil { this.Warningf("could not update local learner with the consensus "+ "result (ignored): %v", err) } } }() // Send propose response with chosen value. response := thispb.ProposeResponse{} response.ChosenValue = chosen message := thispb.PaxosMessage{} message.ProposeResponse = &response errSend := msg.SendResponseProto(this.msn, header, &message) if errSend != nil { this.Errorf("could not send propose response: %v", errSend) return errSend } return nil }