use of io.atomix.protocols.raft.cluster.impl.RaftMemberContext in project atomix by atomix.
the class LeaderAppender method appendEntries.
/**
* Registers a commit handler for the given commit index.
*
* @param index The index for which to register the handler.
* @return A completable future to be completed once the given log index has been committed.
*/
public CompletableFuture<Long> appendEntries(long index) {
raft.checkThread();
if (index == 0) {
return appendEntries();
}
if (index <= raft.getCommitIndex()) {
return CompletableFuture.completedFuture(index);
}
// If there are no other stateful servers in the cluster, immediately commit the index.
if (raft.getCluster().getActiveMemberStates().isEmpty() && raft.getCluster().getPassiveMemberStates().isEmpty()) {
long previousCommitIndex = raft.getCommitIndex();
raft.setCommitIndex(index);
completeCommits(previousCommitIndex, index);
return CompletableFuture.completedFuture(index);
} else // The updated commit index will be sent to passive/reserve members on heartbeats.
if (raft.getCluster().getActiveMemberStates().isEmpty()) {
long previousCommitIndex = raft.getCommitIndex();
raft.setCommitIndex(index);
completeCommits(previousCommitIndex, index);
return CompletableFuture.completedFuture(index);
}
// Only send entry-specific AppendRequests to active members of the cluster.
return appendFutures.computeIfAbsent(index, i -> {
for (RaftMemberContext member : raft.getCluster().getActiveMemberStates()) {
appendEntries(member);
}
return new CompletableFuture<>();
});
}
use of io.atomix.protocols.raft.cluster.impl.RaftMemberContext in project atomix by atomix.
the class LeaderAppender method appendEntries.
/**
* Triggers a heartbeat to a majority of the cluster.
* <p>
* For followers to which no AppendRequest is currently being sent, a new empty AppendRequest will be
* created and sent. For followers to which an AppendRequest is already being sent, the appendEntries()
* call will piggyback on the *next* AppendRequest. Thus, multiple calls to this method will only ever
* result in a single AppendRequest to each follower at any given time, and the returned future will be
* shared by all concurrent calls.
*
* @return A completable future to be completed the next time a heartbeat is received by a majority of the cluster.
*/
public CompletableFuture<Long> appendEntries() {
raft.checkThread();
// If there are no other active members in the cluster, simply complete the append operation.
if (raft.getCluster().getRemoteMemberStates().isEmpty()) {
return CompletableFuture.completedFuture(null);
}
// Create a heartbeat future and add it to the heartbeat futures list.
TimestampedFuture<Long> future = new TimestampedFuture<>();
heartbeatFutures.add(future);
// Iterate through members and append entries. Futures will be completed on responses from followers.
for (RaftMemberContext member : raft.getCluster().getRemoteMemberStates()) {
appendEntries(member);
}
return future;
}
use of io.atomix.protocols.raft.cluster.impl.RaftMemberContext in project atomix by atomix.
the class LeaderRole method onTransfer.
@Override
public CompletableFuture<TransferResponse> onTransfer(final TransferRequest request) {
logRequest(request);
RaftMemberContext member = raft.getCluster().getMemberState(request.member());
if (member == null) {
return CompletableFuture.completedFuture(logResponse(TransferResponse.builder().withStatus(RaftResponse.Status.ERROR).withError(RaftError.Type.ILLEGAL_MEMBER_STATE).build()));
}
transferring = true;
CompletableFuture<TransferResponse> future = new CompletableFuture<>();
appender.appendEntries(raft.getLogWriter().getLastIndex()).whenComplete((result, error) -> {
if (isRunning()) {
if (error == null) {
log.debug("Transferring leadership to {}", request.member());
raft.transition(RaftServer.Role.FOLLOWER);
future.complete(logResponse(TransferResponse.builder().withStatus(RaftResponse.Status.OK).build()));
} else if (error instanceof CompletionException && error.getCause() instanceof RaftException) {
future.complete(logResponse(TransferResponse.builder().withStatus(RaftResponse.Status.ERROR).withError(((RaftException) error.getCause()).getType(), error.getMessage()).build()));
} else if (error instanceof RaftException) {
future.complete(logResponse(TransferResponse.builder().withStatus(RaftResponse.Status.ERROR).withError(((RaftException) error).getType(), error.getMessage()).build()));
} else {
future.complete(logResponse(TransferResponse.builder().withStatus(RaftResponse.Status.ERROR).withError(RaftError.Type.PROTOCOL_ERROR, error.getMessage()).build()));
}
} else {
future.complete(logResponse(TransferResponse.builder().withStatus(RaftResponse.Status.ERROR).withError(RaftError.Type.ILLEGAL_MEMBER_STATE).build()));
}
});
return future;
}
use of io.atomix.protocols.raft.cluster.impl.RaftMemberContext in project atomix by atomix.
the class CandidateRole method sendVoteRequests.
/**
* Resets the election timer.
*/
private void sendVoteRequests() {
raft.checkThread();
// simply skip the election.
if (!isRunning()) {
return;
}
// Cancel the current timer task and purge the election timer of cancelled tasks.
if (currentTimer != null) {
currentTimer.cancel();
}
// When the election timer is reset, increment the current term and
// restart the election.
raft.setTerm(raft.getTerm() + 1);
raft.setLastVotedFor(raft.getCluster().getMember().nodeId());
final AtomicBoolean complete = new AtomicBoolean();
final Set<DefaultRaftMember> votingMembers = new HashSet<>(raft.getCluster().getActiveMemberStates().stream().map(RaftMemberContext::getMember).collect(Collectors.toList()));
// If there are no other members in the cluster, immediately transition to leader.
if (votingMembers.isEmpty()) {
log.debug("Single member cluster. Transitioning directly to leader.", raft.getCluster().getMember().nodeId());
raft.transition(RaftServer.Role.LEADER);
return;
}
// Send vote requests to all nodes. The vote request that is sent
// to this node will be automatically successful.
// First check if the quorum is null. If the quorum isn't null then that
// indicates that another vote is already going on.
final Quorum quorum = new Quorum(raft.getCluster().getQuorum(), (elected) -> {
if (!isRunning()) {
return;
}
complete.set(true);
if (elected) {
raft.transition(RaftServer.Role.LEADER);
} else {
raft.transition(RaftServer.Role.FOLLOWER);
}
});
Duration delay = raft.getElectionTimeout().plus(Duration.ofMillis(random.nextInt((int) raft.getElectionTimeout().toMillis())));
currentTimer = raft.getThreadContext().schedule(delay, () -> {
if (!complete.get()) {
// When the election times out, clear the previous majority vote
// check and restart the election.
log.debug("Election timed out");
quorum.cancel();
sendVoteRequests();
log.debug("Restarted election");
}
});
// First, load the last log entry to get its term. We load the entry
// by its index since the index is required by the protocol.
final Indexed<RaftLogEntry> lastEntry = raft.getLogWriter().getLastEntry();
final long lastTerm;
if (lastEntry != null) {
lastTerm = lastEntry.entry().term();
} else {
lastTerm = 0;
}
log.debug("Requesting votes for term {}", raft.getTerm());
// of the cluster and vote each member for a vote.
for (DefaultRaftMember member : votingMembers) {
log.debug("Requesting vote from {} for term {}", member, raft.getTerm());
VoteRequest request = VoteRequest.builder().withTerm(raft.getTerm()).withCandidate(raft.getCluster().getMember().nodeId()).withLastLogIndex(lastEntry != null ? lastEntry.index() : 0).withLastLogTerm(lastTerm).build();
raft.getProtocol().vote(member.nodeId(), request).whenCompleteAsync((response, error) -> {
raft.checkThread();
if (isRunning() && !complete.get()) {
if (error != null) {
log.warn(error.getMessage());
quorum.fail();
} else {
if (response.term() > raft.getTerm()) {
log.debug("Received greater term from {}", member);
raft.setTerm(response.term());
complete.set(true);
raft.transition(RaftServer.Role.FOLLOWER);
} else if (!response.voted()) {
log.debug("Received rejected vote from {}", member);
quorum.fail();
} else if (response.term() != raft.getTerm()) {
log.debug("Received successful vote for a different term from {}", member);
quorum.fail();
} else {
log.debug("Received successful vote from {}", member);
quorum.succeed();
}
}
}
}, raft.getThreadContext());
}
}
use of io.atomix.protocols.raft.cluster.impl.RaftMemberContext in project atomix by atomix.
the class LeaderRole method onPoll.
@Override
public CompletableFuture<PollResponse> onPoll(final PollRequest request) {
logRequest(request);
// If a member sends a PollRequest to the leader, that indicates that it likely healed from
// a network partition and may have had its status set to UNAVAILABLE by the leader. In order
// to ensure heartbeats are immediately stored to the member, update its status if necessary.
RaftMemberContext member = raft.getCluster().getMemberState(request.candidate());
if (member != null) {
member.resetFailureCount();
}
return CompletableFuture.completedFuture(logResponse(PollResponse.builder().withStatus(RaftResponse.Status.OK).withTerm(raft.getTerm()).withAccepted(false).build()));
}
Aggregations