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;
}
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);
}
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));
}
}
}
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();
}
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();
}
Aggregations