func (sb *SlotBox) String() string { result := "" for i := uint32(0); i < sb.nextUnknownSlotNumber; i++ { slot, exists := sb.slots[i] if !exists { result += fmt.Sprintf("%d -> \nDOES NOT EXIST\n", slot.Number) } else { result += fmt.Sprintf("%d -> \n%s\n", slot.Number, util.MovesString(slot.Value.Moves)) } } return result }
// Takes a set of moves, finds the majority, manipulates the local game, and // tells the clientTasker to broadcast that state func (gs *gameServer) processMoves() { sizeQueue := make([]int, 0) pendingMoves := make([]lib2048.Move, 0) currentBucketSize := 0 for { select { case proposal := <-gs.newMovesCh: moves := proposal.Moves pendingMoves = append(pendingMoves, moves...) if currentBucketSize == 0 { currentBucketSize = len(moves) } else { sizeQueue = append(sizeQueue, len(moves)) } requiredMoves := currentBucketSize * gs.totalNumGameServers if len(pendingMoves) >= requiredMoves { // Find the majority of the first $requiredMoves moves dirVotes := make(map[lib2048.Direction]int) dirVotes[lib2048.Up] = 0 dirVotes[lib2048.Down] = 0 dirVotes[lib2048.Left] = 0 dirVotes[lib2048.Right] = 0 pendingMovesSubset := pendingMoves[:requiredMoves] pendingMoves = pendingMoves[requiredMoves:] for _, move := range pendingMovesSubset { dirVotes[move.Direction]++ } LOGV.Println("GAME SERVER", gs.id, "currentBucketSize", currentBucketSize) LOGV.Println("GAME SERVER", gs.id, "pendingMovesSubset", util.MovesString(pendingMovesSubset)) var majorityDir lib2048.Direction maxVotes := 0 for dir, votes := range dirVotes { if votes > maxVotes { maxVotes = votes majorityDir = dir } else if votes == maxVotes && dir > majorityDir { majorityDir = dir } } LOGV.Println("GAME SERVER", gs.id, "got majority direction:", majorityDir) // Update the 2048 state gs.game2048.MakeMove(majorityDir) if gs.game2048.IsGameOver() { gs.game2048 = lib2048.NewGame2048() } state := gs.getWrappedState(&majorityDir) gs.stateBroadcastCh <- state // Update the bucket size if len(sizeQueue) > 0 { currentBucketSize = sizeQueue[0] sizeQueue = sizeQueue[1:] } } } } }
// 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{}{} }