// 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 }