+ @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;
+ }
+
+ /**
+ * Returns the identifier of the follower.
+ *
+ * @return the identifier of the follower.
+ */
+ public String getId() {
+ return peerInfo.getId();
+ }
+
+ /**
+ * Returns the index of the next log entry to send to the follower.
+ *
+ * @return index of the follower's next log entry.
+ */
+ public long getNextIndex() {
+ return nextIndex;
+ }
+
+ /**
+ * Returns the index of highest log entry known to be replicated on the follower.
+ *
+ * @return the index of highest log entry.
+ */
+ public long getMatchIndex() {
+ return matchIndex;
+ }
+
+ /**
+ * Checks if the follower is active by comparing the time of the last activity with the election time out. The
+ * follower is active if some activity has occurred for the follower within the election time out interval.
+ *
+ * @return true if follower is active, false otherwise.
+ */
+ public boolean isFollowerActive() {
+ if (peerInfo.getVotingState() == VotingState.VOTING_NOT_INITIALIZED) {
+ return false;
+ }
+
+ long elapsed = stopwatch.elapsed(TimeUnit.MILLISECONDS);
+ return stopwatch.isRunning()
+ && elapsed <= context.getConfigParams().getElectionTimeOutInterval().toMillis();
+ }
+
+ /**
+ * Marks the follower as active. This should be called when some activity has occurred for the follower.
+ */
+ public void markFollowerActive() {
+ if (stopwatch.isRunning()) {
+ stopwatch.reset();
+ }
+ stopwatch.start();
+ }
+
+ /**
+ * Marks the follower as inactive. This should only be called from unit tests.
+ */
+ @VisibleForTesting
+ public void markFollowerInActive() {
+ if (stopwatch.isRunning()) {
+ stopwatch.stop();
+ }
+ }
+
+ /**
+ * Returns the time since the last activity occurred for the follower.
+ *
+ * @return time in nanoseconds since the last activity from the follower.
+ */
+ public long nanosSinceLastActivity() {
+ return stopwatch.elapsed(TimeUnit.NANOSECONDS);
+ }
+
+ /**
+ * This method checks if the next replicate message can be sent to the follower. This is an optimization to avoid
+ * sending duplicate message too frequently if the last replicate message was sent and no reply has been received
+ * yet within the current heart beat interval
+ *
+ * @return true if it is OK to replicate, false otherwise
+ */
+ public boolean okToReplicate() {
+ if (peerInfo.getVotingState() == VotingState.VOTING_NOT_INITIALIZED) {
+ return false;
+ }
+
+ // Return false if we are trying to send duplicate data before the heartbeat interval
+ if (getNextIndex() == lastReplicatedIndex && lastReplicatedStopwatch.elapsed(TimeUnit.MILLISECONDS)
+ < context.getConfigParams().getHeartBeatInterval().toMillis()) {
+ return false;
+ }
+
+ resetLastReplicated();
+ return true;
+ }
+
+ private void resetLastReplicated() {
+ lastReplicatedIndex = getNextIndex();
+ if (lastReplicatedStopwatch.isRunning()) {
+ lastReplicatedStopwatch.reset();
+ }
+ lastReplicatedStopwatch.start();
+ }