Search in sources :

Example 1 with RaftLogEntry

use of io.atomix.protocols.raft.storage.log.entry.RaftLogEntry in project atomix by atomix.

the class PassiveRole method checkPreviousEntry.

/**
 * Checks the previous index of the given AppendRequest, returning a boolean indicating whether to continue
 * handling the request.
 */
protected boolean checkPreviousEntry(AppendRequest request, CompletableFuture<AppendResponse> future) {
    RaftLogWriter writer = raft.getLogWriter();
    RaftLogReader reader = raft.getLogReader();
    // with a zero term in the event the leader has compacted its logs and is sending the first entry.
    if (request.prevLogTerm() != 0) {
        // Get the last entry written to the log.
        Indexed<RaftLogEntry> lastEntry = writer.getLastEntry();
        // If the local log is non-empty...
        if (lastEntry != null) {
            // If the previous log index is greater than the last entry index, fail the attempt.
            if (request.prevLogIndex() > lastEntry.index()) {
                log.debug("Rejected {}: Previous index ({}) is greater than the local log's last index ({})", request, request.prevLogIndex(), lastEntry.index());
                return failAppend(lastEntry.index(), future);
            }
            // If the previous log index is less than the last written entry index, look up the entry.
            if (request.prevLogIndex() < lastEntry.index()) {
                // Reset the reader to the previous log index.
                if (reader.getNextIndex() != request.prevLogIndex()) {
                    reader.reset(request.prevLogIndex());
                }
                // The previous entry should exist in the log if we've gotten this far.
                if (!reader.hasNext()) {
                    log.debug("Rejected {}: Previous entry does not exist in the local log", request);
                    return failAppend(lastEntry.index(), future);
                }
                // Read the previous entry and validate that the term matches the request previous log term.
                Indexed<RaftLogEntry> previousEntry = reader.next();
                if (request.prevLogTerm() != previousEntry.entry().term()) {
                    log.debug("Rejected {}: Previous entry term ({}) does not match local log's term for the same entry ({})", request, request.prevLogTerm(), previousEntry.entry().term());
                    return failAppend(request.prevLogIndex() - 1, future);
                }
            } else // If the previous log term doesn't equal the last entry term, fail the append, sending the prior entry.
            if (request.prevLogTerm() != lastEntry.entry().term()) {
                log.debug("Rejected {}: Previous entry term ({}) does not equal the local log's last term ({})", request, request.prevLogTerm(), lastEntry.entry().term());
                return failAppend(request.prevLogIndex() - 1, future);
            }
        } else {
            // If the previous log index is set and the last entry is null, fail the append.
            if (request.prevLogIndex() > 0) {
                log.debug("Rejected {}: Previous index ({}) is greater than the local log's last index (0)", request, request.prevLogIndex());
                return failAppend(0, future);
            }
        }
    }
    return true;
}
Also used : RaftLogReader(io.atomix.protocols.raft.storage.log.RaftLogReader) RaftLogWriter(io.atomix.protocols.raft.storage.log.RaftLogWriter) RaftLogEntry(io.atomix.protocols.raft.storage.log.entry.RaftLogEntry)

Example 2 with RaftLogEntry

use of io.atomix.protocols.raft.storage.log.entry.RaftLogEntry in project atomix by atomix.

the class PassiveRole method appendEntries.

/**
 * Appends entries from the given AppendRequest.
 */
protected void appendEntries(AppendRequest request, CompletableFuture<AppendResponse> future) {
    // Compute the last entry index from the previous log index and request entry count.
    final long lastEntryIndex = request.prevLogIndex() + request.entries().size();
    // Ensure the commitIndex is not increased beyond the index of the last entry in the request.
    final long commitIndex = Math.max(raft.getCommitIndex(), Math.min(request.commitIndex(), lastEntryIndex));
    // Track the last log index while entries are appended.
    long lastLogIndex = request.prevLogIndex();
    if (!request.entries().isEmpty()) {
        final RaftLogWriter writer = raft.getLogWriter();
        final RaftLogReader reader = raft.getLogReader();
        // Reset the log to the previous index plus one.
        if (request.prevLogTerm() == 0) {
            log.debug("Reset first index to {}", request.prevLogIndex() + 1);
            writer.reset(request.prevLogIndex() + 1);
        }
        // Iterate through entries and append them.
        for (RaftLogEntry entry : request.entries()) {
            long index = ++lastLogIndex;
            // Get the last entry written to the log by the writer.
            Indexed<RaftLogEntry> lastEntry = writer.getLastEntry();
            if (lastEntry != null) {
                // we need to validate that the entry that's already in the log matches this entry.
                if (lastEntry.index() > index) {
                    // Reset the reader to the current entry index.
                    if (reader.getNextIndex() != index) {
                        reader.reset(index);
                    }
                    // If the reader does not have any next entry, that indicates an inconsistency between the reader and writer.
                    if (!reader.hasNext()) {
                        throw new IllegalStateException("Log reader inconsistent with log writer");
                    }
                    // Read the existing entry from the log.
                    Indexed<RaftLogEntry> existingEntry = reader.next();
                    // the log and append the leader's entry.
                    if (existingEntry.entry().term() != entry.term()) {
                        writer.truncate(index - 1);
                        if (!appendEntry(index, entry, writer, future)) {
                            return;
                        }
                    }
                } else // to read the entry from disk and can just compare the last entry in the writer.
                if (lastEntry.index() == index) {
                    // the log and append the leader's entry.
                    if (lastEntry.entry().term() != entry.term()) {
                        writer.truncate(index - 1);
                        if (!appendEntry(index, entry, writer, future)) {
                            return;
                        }
                    }
                } else // Otherwise, this entry is being appended at the end of the log.
                {
                    // If the last entry index isn't the previous index, throw an exception because something crazy happened!
                    if (lastEntry.index() != index - 1) {
                        throw new IllegalStateException("Log writer inconsistent with next append entry index " + index);
                    }
                    // Append the entry and log a message.
                    if (!appendEntry(index, entry, writer, future)) {
                        return;
                    }
                }
            } else // Otherwise, if the last entry is null just append the entry and log a message.
            {
                if (!appendEntry(index, entry, writer, future)) {
                    return;
                }
            }
            // If the last log index meets the commitIndex, break the append loop to avoid appending uncommitted entries.
            if (!role().active() && index == commitIndex) {
                break;
            }
        }
    }
    // Set the first commit index.
    raft.setFirstCommitIndex(request.commitIndex());
    // Update the context commit and global indices.
    long previousCommitIndex = raft.setCommitIndex(commitIndex);
    if (previousCommitIndex < commitIndex) {
        log.trace("Committed entries up to index {}", commitIndex);
        raft.getServiceManager().applyAll(commitIndex);
    }
    // Return a successful append response.
    succeedAppend(lastLogIndex, future);
}
Also used : RaftLogReader(io.atomix.protocols.raft.storage.log.RaftLogReader) RaftLogWriter(io.atomix.protocols.raft.storage.log.RaftLogWriter) RaftLogEntry(io.atomix.protocols.raft.storage.log.entry.RaftLogEntry)

Example 3 with RaftLogEntry

use of io.atomix.protocols.raft.storage.log.entry.RaftLogEntry in project atomix by atomix.

the class RaftServiceManager method applyIndex.

/**
 * Applies the next entry in the log up to the given index.
 *
 * @param index the index up to which to apply the entry
 */
@SuppressWarnings("unchecked")
private void applyIndex(long index) {
    // Apply entries prior to this entry.
    if (reader.hasNext() && reader.getNextIndex() == index) {
        // Read the entry from the log. If the entry is non-null then apply it, otherwise
        // simply update the last applied index and return a null result.
        Indexed<RaftLogEntry> entry = reader.next();
        try {
            if (entry.index() != index) {
                throw new IllegalStateException("inconsistent index applying entry " + index + ": " + entry);
            }
            CompletableFuture future = futures.remove(index);
            apply(entry).whenComplete((r, e) -> {
                if (future != null) {
                    if (e == null) {
                        future.complete(r);
                    } else {
                        future.completeExceptionally(e);
                    }
                }
            });
        } catch (Exception e) {
            logger.error("Failed to apply {}: {}", entry, e);
        } finally {
            raft.setLastApplied(index);
        }
    } else {
        CompletableFuture future = futures.remove(index);
        if (future != null) {
            logger.error("Cannot apply index " + index);
            future.completeExceptionally(new IndexOutOfBoundsException("Cannot apply index " + index));
        }
    }
}
Also used : CompletableFuture(java.util.concurrent.CompletableFuture) RaftException(io.atomix.protocols.raft.RaftException) RaftLogEntry(io.atomix.protocols.raft.storage.log.entry.RaftLogEntry)

Example 4 with RaftLogEntry

use of io.atomix.protocols.raft.storage.log.entry.RaftLogEntry in project atomix by atomix.

the class AbstractAppender method buildAppendEntriesRequest.

/**
 * Builds a populated AppendEntries request.
 */
@SuppressWarnings("unchecked")
protected AppendRequest buildAppendEntriesRequest(RaftMemberContext member, long lastIndex) {
    final RaftLogReader reader = member.getLogReader();
    final Indexed<RaftLogEntry> prevEntry = reader.getCurrentEntry();
    final DefaultRaftMember leader = raft.getLeader();
    AppendRequest.Builder builder = AppendRequest.builder().withTerm(raft.getTerm()).withLeader(leader != null ? leader.nodeId() : null).withPrevLogIndex(prevEntry != null ? prevEntry.index() : reader.getFirstIndex() - 1).withPrevLogTerm(prevEntry != null ? prevEntry.entry().term() : 0).withCommitIndex(raft.getCommitIndex());
    // Build a list of entries to send to the member.
    final List<RaftLogEntry> entries = new ArrayList<>();
    // Build a list of entries up to the MAX_BATCH_SIZE. Note that entries in the log may
    // be null if they've been compacted and the member to which we're sending entries is just
    // joining the cluster or is otherwise far behind. Null entries are simply skipped and not
    // counted towards the size of the batch.
    // If there exists an entry in the log with size >= MAX_BATCH_SIZE the logic ensures that
    // entry will be sent in a batch of size one
    int size = 0;
    // Iterate through the log until the last index or the end of the log is reached.
    while (reader.hasNext()) {
        // Otherwise, read the next entry and add it to the batch.
        Indexed<RaftLogEntry> entry = reader.next();
        entries.add(entry.entry());
        size += entry.size();
        if (entry.index() == lastIndex || size >= MAX_BATCH_SIZE) {
            break;
        }
    }
    // Add the entries to the request builder and build the request.
    return builder.withEntries(entries).build();
}
Also used : DefaultRaftMember(io.atomix.protocols.raft.cluster.impl.DefaultRaftMember) RaftLogReader(io.atomix.protocols.raft.storage.log.RaftLogReader) ArrayList(java.util.ArrayList) AppendRequest(io.atomix.protocols.raft.protocol.AppendRequest) RaftLogEntry(io.atomix.protocols.raft.storage.log.entry.RaftLogEntry)

Example 5 with RaftLogEntry

use of io.atomix.protocols.raft.storage.log.entry.RaftLogEntry in project atomix by atomix.

the class AbstractAppender method buildAppendEmptyRequest.

/**
 * Builds an empty AppendEntries request.
 * <p>
 * Empty append requests are used as heartbeats to followers.
 */
protected AppendRequest buildAppendEmptyRequest(RaftMemberContext member) {
    final RaftLogReader reader = member.getLogReader();
    // Read the previous entry from the reader.
    // The reader can be null for RESERVE members.
    Indexed<RaftLogEntry> prevEntry = reader != null ? reader.getCurrentEntry() : null;
    DefaultRaftMember leader = raft.getLeader();
    return AppendRequest.builder().withTerm(raft.getTerm()).withLeader(leader != null ? leader.nodeId() : null).withPrevLogIndex(prevEntry != null ? prevEntry.index() : reader != null ? reader.getFirstIndex() - 1 : 0).withPrevLogTerm(prevEntry != null ? prevEntry.entry().term() : 0).withEntries(Collections.emptyList()).withCommitIndex(raft.getCommitIndex()).build();
}
Also used : DefaultRaftMember(io.atomix.protocols.raft.cluster.impl.DefaultRaftMember) RaftLogReader(io.atomix.protocols.raft.storage.log.RaftLogReader) RaftLogEntry(io.atomix.protocols.raft.storage.log.entry.RaftLogEntry)

Aggregations

RaftLogEntry (io.atomix.protocols.raft.storage.log.entry.RaftLogEntry)7 DefaultRaftMember (io.atomix.protocols.raft.cluster.impl.DefaultRaftMember)4 RaftLogReader (io.atomix.protocols.raft.storage.log.RaftLogReader)4 RaftLogWriter (io.atomix.protocols.raft.storage.log.RaftLogWriter)2 Quorum (io.atomix.protocols.raft.utils.Quorum)2 AtomicBoolean (java.util.concurrent.atomic.AtomicBoolean)2 RaftException (io.atomix.protocols.raft.RaftException)1 RaftMemberContext (io.atomix.protocols.raft.cluster.impl.RaftMemberContext)1 AppendRequest (io.atomix.protocols.raft.protocol.AppendRequest)1 PollRequest (io.atomix.protocols.raft.protocol.PollRequest)1 VoteRequest (io.atomix.protocols.raft.protocol.VoteRequest)1 Duration (java.time.Duration)1 ArrayList (java.util.ArrayList)1 HashSet (java.util.HashSet)1 CompletableFuture (java.util.concurrent.CompletableFuture)1