예제 #1
0
func (lp *libpaxos) ReceivePrepare(args *paxosrpc.ReceivePrepareArgs, reply *paxosrpc.ReceivePrepareReply) error {
	if lp.interruptFunc != nil {
		lp.interruptFunc(lp.myNode.ID, Prepare, lp.slotBox.nextUnknownSlotNumber)
	}

	lp.dataMutex.Lock()

	lp.slotBoxMutex.Lock()
	slot := lp.slotBox.Get(args.CommandSlotNumber)
	lp.slotBoxMutex.Unlock()
	if slot != nil {
		// The Proposer will suggest a slot number for its proposal. If that slot
		// number has already been decided upon, tell the Proposer, and give it
		// the decided value, so it can update its own slot box and choose a
		// different slot number.
		reply.Status = paxosrpc.DecidedValueExists
		reply.DecidedSlotNumber = slot.Number
		reply.DecidedValue = *slot.Value
	} else if lp.highestProposalNumberSeen != nil && args.ProposalNumber.LessThan(lp.highestProposalNumberSeen) {
		// If the proposal number is not highest, reject automatically.
		reply.Status = paxosrpc.Reject
	} else {
		// If the proposal number is highest, then OK it, but also send back
		// any accepted proposals.
		lp.highestProposalNumberSeen = &args.ProposalNumber
		reply.Status = paxosrpc.OK
		if lp.highestAcceptedProposal != nil {
			reply.HasAcceptedProposal = true
			reply.AcceptedProposal = *lp.highestAcceptedProposal
		} else {
			reply.HasAcceptedProposal = false
		}
	}
	lp.dataMutex.Unlock()
	return nil
}
예제 #2
0
// doPropose will keep attempting to submit a proposal to the Paxos cluster
// with the given value. This may not always succeed because there will be
// competing proposals, but when doPropose returns, it guarantees that the
// value has been decided in a quorum.
func (lp *libpaxos) doPropose(value *paxosrpc.ProposalValue, doneCh chan<- struct{}) {
	LOGV.Println("doing propose")
	done := false // true if $moves has been Decided, false otherwise.
	for !done {
		retry := false // true if we should try again, for whatever reason, false otherwise.

		// PHASE 1
		LOGV.Println("Proposer", lp.myNode.ID, ": PHASE 1")

		// Make a new proposal such that my_n > n_h
		lp.dataMutex.Lock()
		lp.slotBoxMutex.Lock()
		myProp := paxosrpc.NewProposal(lp.highestProposalNumberSeen.Number+1, lp.slotBox.GetNextUnknownSlotNumber(), lp.myNode.ID, *value)
		lp.highestProposalNumberSeen = &myProp.Number
		lp.slotBoxMutex.Unlock()
		lp.dataMutex.Unlock()

		// Send proposal to everybody
		promisedCount := 1 // include myself
		var otherProposal *paxosrpc.Proposal
		lp.nodesMutex.Lock()
		for _, node := range lp.nodes {
			if node.Info.ID == lp.myNode.ID {
				continue // skip myself
			}

			client := node.getRPCClient()
			if client == nil {
				continue
			}

			args := &paxosrpc.ReceivePrepareArgs{lp.myNode, myProp.Number, myProp.CommandSlotNumber}
			var reply paxosrpc.ReceivePrepareReply
			reply.Status = paxosrpc.Reject

			timedOut, err := rpcCallWithTimeout(client, "PaxosNode.ReceivePrepare", args, &reply)
			if err != nil {
				node.Client = nil // so it will try to redial in future attempts
				continue          // skip nodes that are unreachable
			} else if timedOut {
				LOGE.Println("RPC call PaxosNode.ReceivePrepare to", node.Info.ID, "timed out")
			}

			lp.dataMutex.Lock()
			switch reply.Status {
			case paxosrpc.OK:
				if reply.HasAcceptedProposal {
					if otherProposal == nil || reply.AcceptedProposal.Number.GreaterThan(&otherProposal.Number) {
						otherProposal = &reply.AcceptedProposal
					}
				}
				promisedCount++

			case paxosrpc.DecidedValueExists:
				// Oops, better fill in that value
				lp.slotBoxMutex.Lock()
				lp.slotBox.Add(NewSlot(reply.DecidedSlotNumber, &reply.DecidedValue))
				lp.slotBoxMutex.Unlock()

				lp.triggerHandlerCallCh <- struct{}{}

				retry = true
				break

			case paxosrpc.Reject:
				// do nothing if REJECTED
			}
			lp.dataMutex.Unlock()
		}
		lp.nodesMutex.Unlock()

		// Retry?
		if retry {
			continue
		}

		// Got majority?
		if promisedCount < lp.majorityCount {
			LOGV.Println(lp.myNode.ID, " couldn't get a majority. Got", promisedCount, "needed", lp.majorityCount)
			// Backoff
			num := time.Duration(rand.Int()%100 + 25)
			time.Sleep(num * time.Millisecond)
			continue // try again
		}

		// This is the proposal that we will be sending out in PHASE 2
		propToAccept := otherProposal

		if otherProposal == nil {
			propToAccept = myProp
		}
		LOGV.Println(lp.myNode.ID, "got a majority on", propToAccept.Number.String())

		// PHASE 2
		LOGV.Println("Proposer", lp.myNode.ID, ": PHASE 2")

		// Send <accept, myn, V> to all nodes
		acceptedCount := 1
		lp.nodesMutex.Lock()
		for _, node := range lp.nodes {
			if node.Info.ID == lp.myNode.ID {
				continue // skip myself
			}

			client := node.getRPCClient()
			if client == nil {
				continue
			}

			args := &paxosrpc.ReceiveAcceptArgs{*propToAccept}
			var reply paxosrpc.ReceiveAcceptReply
			reply.Status = paxosrpc.Reject

			timedOut, err := rpcCallWithTimeout(client, "PaxosNode.ReceiveAccept", args, &reply)
			if err != nil {
				LOGE.Println(err)
				node.Client = nil // so it will try to redial in future attempts
				continue          // skip nodes that are unreachable
			} else if timedOut {
				LOGE.Println("RPC call PaxosNode.ReceiveAccept to", node.Info.ID, "timed out")
			}

			if reply.Status == paxosrpc.OK {
				acceptedCount++
			} else {
				LOGV.Println("Node", node.Info.ID, "rejected my Accept message with status", reply.Status)
			} // do nothing if REJECTED
		}
		lp.nodesMutex.Unlock()

		// Got majority?
		if acceptedCount < lp.majorityCount {
			LOGV.Println("Couldn't get majority for PHASE 2,", acceptedCount, "/", lp.majorityCount)
			// Backoff
			num := time.Duration(rand.Int()%100 + 25)
			time.Sleep(num * time.Millisecond)
			continue // try again
		}

		LOGV.Printf("%d got majority (%d/%d) on ACCEPT for slot %d, seqnum is %s, value is\n%s\n", lp.myNode.ID, acceptedCount, lp.majorityCount, propToAccept.CommandSlotNumber, propToAccept.Number.String(), util.MovesString(propToAccept.Value.Moves))

		// Send <decide, va> to all nodes
		lp.nodesMutex.Lock()
		for _, node := range lp.nodes {
			if node.Info.ID == lp.myNode.ID {
				continue // skip myself
			}

			client := node.getRPCClient()
			if client == nil {
				continue
			}

			args := &paxosrpc.ReceiveDecideArgs{*propToAccept}
			var reply paxosrpc.ReceiveDecideReply

			rpcCallWithTimeout(client, "PaxosNode.ReceiveDecide", args, &reply)
			// We don't care if that rpc call had an error or timed out
		}

		LOGV.Printf("%d decided on slot %d, seqnum is %s, value is\n%s\n", lp.myNode.ID, propToAccept.CommandSlotNumber, propToAccept.Number.String(), util.MovesString(propToAccept.Value.Moves))
		lp.nodesMutex.Unlock()

		lp.dataMutex.Lock()
		lp.highestAcceptedProposal = nil
		lp.dataMutex.Unlock()

		lp.slotBoxMutex.Lock()
		lp.slotBox.Add(NewSlot(propToAccept.CommandSlotNumber, &propToAccept.Value))
		lp.slotBoxMutex.Unlock()

		lp.triggerHandlerCallCh <- struct{}{}

		if propToAccept.Number.Equal(&myProp.Number) {
			done = true
		}
	}
	LOGV.Println("Done propose")
	doneCh <- struct{}{}
}