// Propose proposes given value for a consensus. // // value: The value to propose for consensus. // // timeout: Maximum time duration for the propose operation. // // Returns the chosen value on success. func (this *Paxos) Propose(value []byte, timeout time.Duration) ( []byte, error) { // If local instance is not a proposer, find a random proposer. proposer := this.msn.UID() if !this.IsProposer() { proposer = this.proposerList[rand.Intn(len(this.proposerList))] this.Infof("using %s as the proposer", proposer) } // Send the propose request. request := thispb.ProposeRequest{} request.ProposedValue = value message := thispb.PaxosMessage{} message.ProposeRequest = &request reqHeader := this.msn.NewRequest(this.namespace, this.uid, "ClassicPaxos.Propose", timeout) errSend := msg.SendProto(this.msn, proposer, reqHeader, &message) if errSend != nil { this.Errorf("could not send propose request to %s: %v", proposer, errSend) return nil, errSend } // Wait for the response. _, errRecv := msg.ReceiveProto(this.msn, reqHeader, &message) if errRecv != nil { this.Errorf("could not receive propose response from %s: %v", proposer, errRecv) return nil, errRecv } if message.ProposeResponse == nil { this.Errorf("propose response from %s is empty", proposer) return nil, errs.ErrCorrupt } response := message.GetProposeResponse() return response.GetChosenValue(), 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 }