Search in sources :

Example 1 with RaftMemberContext

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<>();
    });
}
Also used : CompletableFuture(java.util.concurrent.CompletableFuture) RaftMemberContext(io.atomix.protocols.raft.cluster.impl.RaftMemberContext)

Example 2 with RaftMemberContext

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;
}
Also used : RaftMemberContext(io.atomix.protocols.raft.cluster.impl.RaftMemberContext)

Example 3 with RaftMemberContext

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;
}
Also used : CompletableFuture(java.util.concurrent.CompletableFuture) RaftException(io.atomix.protocols.raft.RaftException) RaftMemberContext(io.atomix.protocols.raft.cluster.impl.RaftMemberContext) TransferResponse(io.atomix.protocols.raft.protocol.TransferResponse) CompletionException(java.util.concurrent.CompletionException)

Example 4 with RaftMemberContext

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());
    }
}
Also used : AtomicBoolean(java.util.concurrent.atomic.AtomicBoolean) Quorum(io.atomix.protocols.raft.utils.Quorum) DefaultRaftMember(io.atomix.protocols.raft.cluster.impl.DefaultRaftMember) RaftMemberContext(io.atomix.protocols.raft.cluster.impl.RaftMemberContext) VoteRequest(io.atomix.protocols.raft.protocol.VoteRequest) Duration(java.time.Duration) HashSet(java.util.HashSet) RaftLogEntry(io.atomix.protocols.raft.storage.log.entry.RaftLogEntry)

Example 5 with RaftMemberContext

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()));
}
Also used : RaftMemberContext(io.atomix.protocols.raft.cluster.impl.RaftMemberContext)

Aggregations

RaftMemberContext (io.atomix.protocols.raft.cluster.impl.RaftMemberContext)5 CompletableFuture (java.util.concurrent.CompletableFuture)2 RaftException (io.atomix.protocols.raft.RaftException)1 DefaultRaftMember (io.atomix.protocols.raft.cluster.impl.DefaultRaftMember)1 TransferResponse (io.atomix.protocols.raft.protocol.TransferResponse)1 VoteRequest (io.atomix.protocols.raft.protocol.VoteRequest)1 RaftLogEntry (io.atomix.protocols.raft.storage.log.entry.RaftLogEntry)1 Quorum (io.atomix.protocols.raft.utils.Quorum)1 Duration (java.time.Duration)1 HashSet (java.util.HashSet)1 CompletionException (java.util.concurrent.CompletionException)1 AtomicBoolean (java.util.concurrent.atomic.AtomicBoolean)1