private async Task Candidacy(CancellationToken c) { while (_role == Role.Candidate) { var forMe = 1; // vote for yourself var againstMe = 0; var peers = _peerManager.GetPeers().ToArray(); var concensus = (peers.Length / 2) + 1; var proxies = peers.Select(x => _peerManager.GetProxy(x.Address)); var retry = TheTrace.LogPolicy(_meAsAPeer.ShortName).WaitAndRetryAsync(3, (i) => TimeSpan.FromMilliseconds(i * i * 30)); var policy = Policy.TimeoutAsync(_settings.CandidacyTimeout).WrapAsync(retry); var request = new RequestVoteRequest() { CandidateId = State.Id, CurrentTerm = State.CurrentTerm, LastLogIndex = _logPersister.LastIndex, LastLogTerm = _logPersister.LastEntryTerm }; var all = await Task.WhenAll(proxies.Select(p => policy.ExecuteAndCaptureAsync(() => p.RequestVoteAsync(request)))); var maxTerm = 0L; foreach (var r in all) { if (r.Outcome == OutcomeType.Successful) { if (r.Result.CurrentTerm > maxTerm) { maxTerm = r.Result.CurrentTerm; } if (r.Result != null && r.Result.VoteGranted) { forMe++; } else { againstMe++; } } } if (againstMe >= concensus) { TheTrace.TraceInformation($"[{_meAsAPeer.ShortName}] Result of the candidacy for term {State.CurrentTerm}. I got rejected with {againstMe} votes :/"); BecomeFollower(maxTerm); } else if (forMe >= concensus) { TheTrace.TraceInformation($"[{_meAsAPeer.ShortName}] Result of the candidacy for term {State.CurrentTerm}. I got elected with {forMe} votes! :)"); BecomeLeader(); } else { TheTrace.TraceInformation($"[{_meAsAPeer.ShortName}] Result of the candidacy for term {State.CurrentTerm}. Non-conclusive with {forMe} for me and {againstMe} against me."); } } }
/// <inheritdoc /> public Task <RequestVoteResponse> RequestVoteAsync(RequestVoteRequest request) { var peers = _peerManager.GetPeers(); var peer = peers.Where(x => x.Id == request.CandidateId).FirstOrDefault(); var peerName = peer?.ShortName ?? request.CandidateId.ToString(); lock (State) { if (request.CurrentTerm > State.CurrentTerm) { BecomeFollower(request.CurrentTerm); } // Reply false if term < currentTerm if (State.CurrentTerm > request.CurrentTerm) { TheTrace.TraceInformation($"[{_meAsAPeer.ShortName}] Rejecting vote of {peerName} due to backward term"); return(Task.FromResult(new RequestVoteResponse() { CurrentTerm = State.CurrentTerm, VoteGranted = false })); } // If votedFor is null or candidateId, and candidate’s log is at least as up-to-date as receiver’s log, grant vote(§5.2, §5.4) if (!State.LastVotedForId.HasValue && _logPersister.LastIndex <= request.LastLogIndex) { State.LastVotedForId = request.CandidateId; // If election timeout elapses without receiving AppendEntries RPC from current leader OR GRANTING VOTE TO CANDIDATE: convert to candidate _lastHeartbeat.Set(); TheTrace.TraceInformation($"[{_meAsAPeer.ShortName}] Voting for {peerName} for term {request.CurrentTerm}"); return(Task.FromResult(new RequestVoteResponse() { CurrentTerm = State.CurrentTerm, VoteGranted = true })); } TheTrace.TraceInformation($"[{_meAsAPeer.ShortName}] Rejecting vote of {peerName} for term {request.CurrentTerm} as it did not fulfil"); // assume the rest we send back no return(Task.FromResult(new RequestVoteResponse() { CurrentTerm = State.CurrentTerm, VoteGranted = false })); } }