RATIS-2044. Fix ReadIndex loss caused by data race in AppendEntriesListeners (#1052)

diff --git a/ratis-server/src/main/java/org/apache/ratis/server/impl/ReadIndexHeartbeats.java b/ratis-server/src/main/java/org/apache/ratis/server/impl/ReadIndexHeartbeats.java
index d08a1ea..4ff1460 100644
--- a/ratis-server/src/main/java/org/apache/ratis/server/impl/ReadIndexHeartbeats.java
+++ b/ratis-server/src/main/java/org/apache/ratis/server/impl/ReadIndexHeartbeats.java
@@ -23,6 +23,7 @@
 import org.apache.ratis.server.raftlog.RaftLog;
 import org.apache.ratis.server.raftlog.RaftLogIndex;
 import org.apache.ratis.util.JavaUtils;
+import org.apache.ratis.util.Preconditions;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -123,8 +124,15 @@
 
   class AppendEntriesListeners {
     private final NavigableMap<Long, AppendEntriesListener> sorted = new TreeMap<>();
+    private Exception exception = null;
 
     synchronized AppendEntriesListener add(long commitIndex, Function<Long, AppendEntriesListener> constructor) {
+      if (exception != null) {
+        Preconditions.assertTrue(sorted.isEmpty());
+        final AppendEntriesListener listener = constructor.apply(commitIndex);
+        listener.getFuture().completeExceptionally(exception);
+        return listener;
+      }
       return sorted.computeIfAbsent(commitIndex, constructor);
     }
 
@@ -152,6 +160,10 @@
     }
 
     synchronized void failAll(Exception e) {
+      if (exception != null) {
+        return;
+      }
+      exception = e;
       sorted.forEach((index, listener) -> listener.getFuture().completeExceptionally(e));
       sorted.clear();
     }