+public final class FollowerLogInformation {
+ public static final long NO_INDEX = -1;
+
+ private final Stopwatch stopwatch = Stopwatch.createUnstarted();
+
+ private final RaftActorContext context;
+
+ private long nextIndex;
+
+ private long matchIndex;
+
+ private long lastReplicatedIndex = -1L;
+
+ private long sentCommitIndex = -1L;
+
+ private final Stopwatch lastReplicatedStopwatch = Stopwatch.createUnstarted();
+
+ private short payloadVersion = -1;
+
+ // Assume the HELIUM_VERSION version initially for backwards compatibility until we obtain the follower's
+ // actual version via AppendEntriesReply. Although we no longer support the Helium version, a pre-Boron
+ // follower will not have the version field in AppendEntriesReply so it will be set to 0 which is
+ // HELIUM_VERSION.
+ private short raftVersion = RaftVersions.HELIUM_VERSION;
+
+ private final PeerInfo peerInfo;
+
+ private LeaderInstallSnapshotState installSnapshotState;
+
+ private long slicedLogEntryIndex = NO_INDEX;
+
+ private boolean needsLeaderAddress;
+
+ /**
+ * Constructs an instance.
+ *
+ * @param peerInfo the associated PeerInfo of the follower.
+ * @param matchIndex the initial match index.
+ * @param context the RaftActorContext.
+ */
+ @VisibleForTesting
+ FollowerLogInformation(final PeerInfo peerInfo, final long matchIndex, final RaftActorContext context) {
+ this.nextIndex = context.getCommitIndex();
+ this.matchIndex = matchIndex;
+ this.context = context;
+ this.peerInfo = requireNonNull(peerInfo);
+ }
+
+ /**
+ * Constructs an instance with no matching index.
+ *
+ * @param peerInfo the associated PeerInfo of the follower.
+ * @param context the RaftActorContext.
+ */
+ public FollowerLogInformation(final PeerInfo peerInfo, final RaftActorContext context) {
+ this(peerInfo, NO_INDEX, context);
+ }
+
+ /**
+ * Increments the value of the follower's next index.
+ *
+ * @return the new value of nextIndex.
+ */
+ @VisibleForTesting
+ long incrNextIndex() {
+ return nextIndex++;
+ }
+
+ /**
+ * Decrements the value of the follower's next index, taking into account its reported last log index.
+ *
+ * @param followerLastIndex follower's last reported index.
+ * @return true if the next index was decremented, i.e. it was previously >= 0, false otherwise.
+ */
+ public boolean decrNextIndex(final long followerLastIndex) {
+ if (nextIndex < 0) {
+ return false;
+ }
+
+ if (followerLastIndex >= 0 && nextIndex > followerLastIndex) {
+ // If the follower's last log index is lower than nextIndex, jump directly to it, so we converge
+ // on a common index more quickly.
+ nextIndex = followerLastIndex;
+ } else {
+ nextIndex--;
+ }
+ return true;
+ }
+
+ /**
+ * Sets the index of the follower's next log entry.
+ *
+ * @param nextIndex the new index.
+ * @return true if the new index differed from the current index and the current index was updated, false
+ * otherwise.
+ */
+ @SuppressWarnings("checkstyle:hiddenField")
+ public boolean setNextIndex(final long nextIndex) {
+ if (this.nextIndex != nextIndex) {
+ this.nextIndex = nextIndex;
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Increments the value of the follower's match index.
+ *
+ * @return the new value of matchIndex.
+ */
+ public long incrMatchIndex() {
+ return matchIndex++;
+ }
+
+ /**
+ * Sets the index of the follower's highest log entry.
+ *
+ * @param matchIndex the new index.
+ * @return true if the new index differed from the current index and the current index was updated, false
+ * otherwise.
+ */
+ @SuppressWarnings("checkstyle:hiddenField")
+ public boolean setMatchIndex(final long matchIndex) {
+ // If the new match index is the index of the entry currently being sliced, then we know slicing is complete
+ // and the follower received the entry and responded so clear the slicedLogEntryIndex
+ if (isLogEntrySlicingInProgress() && slicedLogEntryIndex == matchIndex) {
+ slicedLogEntryIndex = NO_INDEX;
+ }
+
+ if (this.matchIndex != matchIndex) {
+ this.matchIndex = matchIndex;
+ return true;
+ }
+
+ return false;
+ }