This is an automated email from the ASF dual-hosted git repository.

szetszwo pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ratis.git


The following commit(s) were added to refs/heads/master by this push:
     new e5391233e RATIS-2548. Stabilize timing-sensitive Ratis tests (#1475)
e5391233e is described below

commit e5391233e1c9f470ceae76d72acc74f91978463d
Author: Yongzao <[email protected]>
AuthorDate: Sun May 31 23:40:23 2026 +0800

    RATIS-2548. Stabilize timing-sensitive Ratis tests (#1475)
---
 .../counter/server/TestManualRestoreSnapshot.java  | 10 ++--
 .../ratis/InstallSnapshotNotificationTests.java    |  2 +-
 .../org/apache/ratis/LinearizableReadTests.java    | 32 +++++++++--
 .../test/java/org/apache/ratis/RaftBasicTests.java | 62 +++++++++++++++++-----
 .../ratis/server/impl/LeaderElectionTests.java     | 43 ++++++++-------
 .../impl/SimpleStateMachine4Testing.java           | 14 ++---
 .../org/apache/ratis/RaftLogTruncateTests.java     | 16 +++---
 .../TestLinearizableReadRepliedIndexWithGrpc.java  |  9 ++--
 .../cli/sh/ElectionCommandIntegrationTest.java     | 11 ++--
 9 files changed, 134 insertions(+), 65 deletions(-)

diff --git 
a/ratis-examples/src/test/java/org/apache/ratis/examples/counter/server/TestManualRestoreSnapshot.java
 
b/ratis-examples/src/test/java/org/apache/ratis/examples/counter/server/TestManualRestoreSnapshot.java
index 0480a5364..f1075ae08 100644
--- 
a/ratis-examples/src/test/java/org/apache/ratis/examples/counter/server/TestManualRestoreSnapshot.java
+++ 
b/ratis-examples/src/test/java/org/apache/ratis/examples/counter/server/TestManualRestoreSnapshot.java
@@ -112,15 +112,15 @@ public class TestManualRestoreSnapshot extends BaseTest 
implements MiniRaftClust
     final StateMachine stateMachine = restartedFollower.getStateMachine();
     final SnapshotInfo info = stateMachine.getLatestSnapshot();
     LOG.info("{} restarted snapshot info {} from {}", followerId, info, 
stateMachine);
+    assertNotNull(info);
+    assertTrue(info.getTermIndex().equals(applied), () -> info + " != " + 
applied);
 
     JavaUtils.attemptUntilTrue(() -> {
       System.out.println(cluster.printServers());
-      final TermIndex leaderLastApplied = 
leaderStateMachine.getLastAppliedTermIndex();
-      LOG.info("Leader   {} last applied {}", leader.getId(), 
leaderLastApplied);
       final TermIndex followerLastApplied = 
stateMachine.getLastAppliedTermIndex();
-      LOG.info("Follower {} last applied {}", followerId, followerLastApplied);
-      return followerLastApplied.equals(leaderLastApplied);
-    }, 10, TimeDuration.ONE_SECOND, "followerLastApplied", LOG);
+      LOG.info("Follower {} last applied {}, snapshot {}", followerId, 
followerLastApplied, applied);
+      return followerLastApplied != null && followerLastApplied.getIndex() >= 
applied.getIndex();
+    }, 10, TimeDuration.ONE_SECOND, "followerSnapshotApplied", LOG);
 
     sendMessages(cluster, 7);
   }
diff --git 
a/ratis-server/src/test/java/org/apache/ratis/InstallSnapshotNotificationTests.java
 
b/ratis-server/src/test/java/org/apache/ratis/InstallSnapshotNotificationTests.java
index 931bf6317..47e87352c 100644
--- 
a/ratis-server/src/test/java/org/apache/ratis/InstallSnapshotNotificationTests.java
+++ 
b/ratis-server/src/test/java/org/apache/ratis/InstallSnapshotNotificationTests.java
@@ -309,7 +309,7 @@ public abstract class 
InstallSnapshotNotificationTests<CLUSTER extends MiniRaftC
       LOG.info("{}: newLeaderNextIndex = {}", leaderId, newLeaderNextIndex);
       Assertions.assertTrue(newLeaderNextIndex > oldLeaderNextIndex);
       Assertions.assertEquals(newLeaderNextIndex, 
follower.getRaftLog().getNextIndex());
-    }, 10, ONE_SECOND, "followerNextIndex", LOG);
+    }, 60, ONE_SECOND, "followerNextIndex", LOG);
   }
 
   @Test
diff --git 
a/ratis-server/src/test/java/org/apache/ratis/LinearizableReadTests.java 
b/ratis-server/src/test/java/org/apache/ratis/LinearizableReadTests.java
index 832457e9d..b0397e894 100644
--- a/ratis-server/src/test/java/org/apache/ratis/LinearizableReadTests.java
+++ b/ratis-server/src/test/java/org/apache/ratis/LinearizableReadTests.java
@@ -20,6 +20,8 @@ package org.apache.ratis;
 import org.apache.ratis.client.RaftClient;
 import org.apache.ratis.client.RaftClientConfigKeys;
 import org.apache.ratis.conf.RaftProperties;
+import org.apache.ratis.proto.RaftProtos.LogEntryProto;
+import org.apache.ratis.protocol.Message;
 import org.apache.ratis.protocol.RaftClientReply;
 import org.apache.ratis.protocol.RaftPeerId;
 import org.apache.ratis.protocol.exceptions.ReadIndexException;
@@ -30,6 +32,7 @@ import org.apache.ratis.server.RaftServer;
 import org.apache.ratis.server.RaftServerConfigKeys;
 import org.apache.ratis.server.RaftServerConfigKeys.Read.ReadIndex.Type;
 import org.apache.ratis.server.impl.MiniRaftCluster;
+import org.apache.ratis.util.JavaUtils;
 import org.apache.ratis.util.Slf4jUtils;
 import org.apache.ratis.util.TimeDuration;
 import org.apache.ratis.util.function.CheckedConsumer;
@@ -179,8 +182,28 @@ public abstract class LinearizableReadTests<CLUSTER 
extends MiniRaftCluster>
     runWithNewCluster(LinearizableReadTests::runTestFollowerReadOnlyParallel);
   }
 
+  private static long getLogEntryIndex(RaftServer.Division leader, Message 
message, long startIndex) throws Exception {
+    final long nextIndex = leader.getRaftLog().getNextIndex();
+    for (long index = startIndex; index < nextIndex; index++) {
+      final LogEntryProto entry = leader.getRaftLog().get(index);
+      if (entry != null && entry.hasStateMachineLogEntry()
+          && 
message.getContent().equals(entry.getStateMachineLogEntry().getLogData())) {
+        return index;
+      }
+    }
+    throw new AssertionError("Failed to find " + message + " from index " + 
startIndex + " to " + nextIndex);
+  }
+
+  private static void waitForCommitIndex(RaftServer.Division leader, long 
index) throws Exception {
+    JavaUtils.attempt(() -> {
+      final long commitIndex = leader.getRaftLog().getLastCommittedIndex();
+      Assertions.assertTrue(commitIndex >= index, () -> "commitIndex=" + 
commitIndex + " < index=" + index);
+    }, 10, HUNDRED_MILLIS, "commitIndex >= " + index, null);
+  }
+
   static <C extends MiniRaftCluster> void runTestFollowerReadOnlyParallel(C 
cluster) throws Exception {
-    final RaftPeerId leaderId = RaftTestUtil.waitForLeader(cluster).getId();
+    final RaftServer.Division leader = RaftTestUtil.waitForLeader(cluster);
+    final RaftPeerId leaderId = leader.getId();
 
     final List<RaftServer.Division> followers = cluster.getFollowers();
     Assertions.assertEquals(2, followers.size());
@@ -199,9 +222,12 @@ public abstract class LinearizableReadTests<CLUSTER 
extends MiniRaftCluster>
         assertReplyExact(count, leaderClient.io().send(INCREMENT));
 
         count++;
+        final long nextIndex = leader.getRaftLog().getNextIndex();
         writeReplies.add(new Reply(count, 
leaderClient.async().send(WAIT_AND_INCREMENT)));
-        // sleep to let the commitIndex/appliedIndex get updated.
-        Thread.sleep(100);
+        final long waitAndIncrementIndex = JavaUtils.attemptRepeatedly(
+            () -> getLogEntryIndex(leader, WAIT_AND_INCREMENT, nextIndex),
+            10, HUNDRED_MILLIS, "WAIT_AND_INCREMENT entry", null);
+        waitForCommitIndex(leader, waitAndIncrementIndex);
         // WAIT_AND_INCREMENT will delay 500ms to update the count, the read 
must wait for it.
         assertReplyExact(count, f0Client.io().sendReadOnly(QUERY, f0));
         f1Replies.add(new Reply(count, f1Client.async().sendReadOnly(QUERY, 
f1)));
diff --git a/ratis-server/src/test/java/org/apache/ratis/RaftBasicTests.java 
b/ratis-server/src/test/java/org/apache/ratis/RaftBasicTests.java
index c71b57e82..9fff8640a 100644
--- a/ratis-server/src/test/java/org/apache/ratis/RaftBasicTests.java
+++ b/ratis-server/src/test/java/org/apache/ratis/RaftBasicTests.java
@@ -51,6 +51,7 @@ import org.slf4j.Logger;
 import org.slf4j.event.Level;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
@@ -114,6 +115,41 @@ public abstract class RaftBasicTests<CLUSTER extends 
MiniRaftCluster>
     return future;
   }
 
+  private static List<LogEntryProto> findLogEntriesContaining(
+      RaftLog log, long expectedTerm, SimpleMessage[] expectedMessages) {
+    final List<LogEntryProto> entries = 
RaftTestUtil.getStateMachineLogEntries(log, s -> {});
+    final List<LogEntryProto> matched = new 
ArrayList<>(expectedMessages.length);
+    int e = 0;
+    for (SimpleMessage expected : expectedMessages) {
+      boolean found = false;
+      for (; e < entries.size(); e++) {
+        final LogEntryProto entry = entries.get(e);
+        if (entry.getTerm() >= expectedTerm
+            && 
expected.getContent().equals(entry.getStateMachineLogEntry().getLogData())) {
+          matched.add(entry);
+          e++;
+          found = true;
+          break;
+        }
+      }
+      Assertions.assertTrue(found, () -> "Failed to find " + expected + " in 
entries " + entries);
+    }
+    return matched;
+  }
+
+  private static void assertLogEntriesContaining(
+      RaftServer.Division server, long expectedTerm, SimpleMessage[] 
expectedMessages, int numAttempts, Logger log)
+      throws Exception {
+    final String name = server.getId() + " assertLogEntriesContaining";
+    JavaUtils.attempt(() -> {
+          RaftTestUtil.assertLogEntries(
+              findLogEntriesContaining(server.getRaftLog(), expectedTerm, 
expectedMessages),
+              expectedTerm, expectedMessages);
+          return null;
+        },
+        numAttempts, TimeDuration.ONE_SECOND, () -> name, log);
+  }
+
   static void runTestBasicAppendEntries(
       boolean async, boolean killLeader, int numMessages, MiniRaftCluster 
cluster, Logger log)
       throws Exception {
@@ -140,38 +176,36 @@ public abstract class RaftBasicTests<CLUSTER extends 
MiniRaftCluster>
     final SimpleMessage[] messages = SimpleMessage.create(numMessages);
 
     try (final RaftClient client = cluster.createClient()) {
-      final AtomicInteger asyncReplyCount = new AtomicInteger();
-      final CompletableFuture<Void> f = new CompletableFuture<>();
+      final List<CompletableFuture<RaftClientReply>> asyncReplies = new 
ArrayList<>();
 
       for (SimpleMessage message : messages) {
         if (async) {
-          client.async().send(message).thenAcceptAsync(reply -> {
-            if (!reply.isSuccess()) {
-              f.completeExceptionally(
-                  new AssertionError("Failed with reply " + reply));
-            } else if (asyncReplyCount.incrementAndGet() == messages.length) {
-              f.complete(null);
-            }
-          });
+          asyncReplies.add(client.async().send(message));
         } else {
           final RaftClientReply reply = client.io().send(message);
           Assertions.assertTrue(reply.isSuccess());
         }
       }
       if (async) {
-        f.join();
-        Assertions.assertEquals(messages.length, asyncReplyCount.get());
+        asyncReplies.forEach(f -> {
+          final RaftClientReply reply = f.join();
+          Assertions.assertTrue(reply.isSuccess(), () -> "Failed with reply " 
+ reply);
+        });
       }
     }
     Thread.sleep(cluster.getTimeoutMax().toIntExact(TimeUnit.MILLISECONDS) + 
100);
-    log.info(cluster.printAllLogs());
     killAndRestartFollower.join();
     killAndRestartLeader.join();
+    log.info(cluster.printAllLogs());
 
 
     final List<RaftServer.Division> divisions = 
cluster.getServerAliveStream().collect(Collectors.toList());
     for(RaftServer.Division impl: divisions) {
-      RaftTestUtil.assertLogEntries(impl, term, messages, 50, log);
+      if (killLeader) {
+        assertLogEntriesContaining(impl, term, messages, 50, log);
+      } else {
+        RaftTestUtil.assertLogEntries(impl, term, messages, 50, log);
+      }
     }
   }
 
diff --git 
a/ratis-server/src/test/java/org/apache/ratis/server/impl/LeaderElectionTests.java
 
b/ratis-server/src/test/java/org/apache/ratis/server/impl/LeaderElectionTests.java
index 3a47d127c..07b479284 100644
--- 
a/ratis-server/src/test/java/org/apache/ratis/server/impl/LeaderElectionTests.java
+++ 
b/ratis-server/src/test/java/org/apache/ratis/server/impl/LeaderElectionTests.java
@@ -148,29 +148,32 @@ public abstract class LeaderElectionTests<CLUSTER extends 
MiniRaftCluster>
     final MiniRaftCluster cluster = newCluster(3);
     cluster.start();
     RaftTestUtil.waitForLeader(cluster);
-    try (RaftClient client = cluster.createClient()) {
-      for (int i = 0; i < 10; ++i) {
-        RaftClientReply reply = client.io().send(new 
RaftTestUtil.SimpleMessage("message_" + i));
-        assertTrue(reply.isSuccess());
-      }
-      // add 3 new servers and wait longer time
+    try {
       CodeInjectionForTesting.put(RaftServerImpl.START_COMPLETE, new 
SleepCode(2000));
-      final PeerChanges peerChanges = cluster.addNewPeers(2, true);
-      LOG.info("add new 3 servers");
-      LOG.info(cluster.printServers());
-      RaftClientReply reply = 
client.admin().setConfiguration(SetConfigurationRequest.Arguments.newBuilder()
-              .setServersInNewConf(peerChanges.getAddedPeers())
-              .setMode(SetConfigurationRequest.Mode.ADD).build());
-      assertTrue(reply.isSuccess());
-      for (RaftServer server : cluster.getServers()) {
-        RaftServerProxy proxy = (RaftServerProxy) server;
-        proxy.getImpls().forEach(s -> {
-          assertTrue(s.isRunning());
-        });
+      try (RaftClient client = cluster.createClient()) {
+        for (int i = 0; i < 10; ++i) {
+          RaftClientReply reply = client.io().send(new 
RaftTestUtil.SimpleMessage("message_" + i));
+          assertTrue(reply.isSuccess());
+        }
+        // add 3 new servers and wait longer time
+        final PeerChanges peerChanges = cluster.addNewPeers(2, true);
+        LOG.info("add new 3 servers");
+        LOG.info(cluster.printServers());
+        RaftClientReply reply = 
client.admin().setConfiguration(SetConfigurationRequest.Arguments.newBuilder()
+                .setServersInNewConf(peerChanges.getAddedPeers())
+                .setMode(SetConfigurationRequest.Mode.ADD).build());
+        assertTrue(reply.isSuccess());
+        JavaUtils.attempt(() -> {
+          for (RaftServer server : cluster.getServers()) {
+            RaftServerProxy proxy = (RaftServerProxy) server;
+            proxy.getImpls().forEach(s -> assertTrue(s.isRunning()));
+          }
+        }, 10, HUNDRED_MILLIS, "all server impls running", LOG);
       }
+    } finally {
+      cluster.shutdown();
+      CodeInjectionForTesting.remove(RaftServerImpl.START_COMPLETE);
     }
-    cluster.shutdown();;
-    CodeInjectionForTesting.remove(RaftServerImpl.START_COMPLETE);
   }
 
   @Test
diff --git 
a/ratis-server/src/test/java/org/apache/ratis/statemachine/impl/SimpleStateMachine4Testing.java
 
b/ratis-server/src/test/java/org/apache/ratis/statemachine/impl/SimpleStateMachine4Testing.java
index 1ffbdbcb9..a4a7002c3 100644
--- 
a/ratis-server/src/test/java/org/apache/ratis/statemachine/impl/SimpleStateMachine4Testing.java
+++ 
b/ratis-server/src/test/java/org/apache/ratis/statemachine/impl/SimpleStateMachine4Testing.java
@@ -268,14 +268,16 @@ public class SimpleStateMachine4Testing extends 
BaseStateMachine {
     // TODO: snapshot should be written to a tmp file, then renamed
     final File snapshotFile = storage.getSnapshotFile(termIndex.getTerm(), 
endIndex);
     LOG.debug("Taking a snapshot with {}, file:{}", termIndex, snapshotFile);
+    final LogEntryProto[] entries;
+    synchronized (indexMap) {
+      entries = indexMap.values().stream()
+          .filter(entry -> entry.getIndex() <= endIndex)
+          .toArray(LogEntryProto[]::new);
+    }
     try (SegmentedRaftLogOutputStream out = new 
SegmentedRaftLogOutputStream(snapshotFile, false,
         segmentMaxSize, preallocatedSize, 
ByteBuffer.allocateDirect(bufferSize))) {
-      for (final LogEntryProto entry : indexMap.values()) {
-        if (entry.getIndex() > endIndex) {
-          break;
-        } else {
-          out.write(entry);
-        }
+      for (final LogEntryProto entry : entries) {
+        out.write(entry);
       }
       out.flush();
     } catch (IOException e) {
diff --git 
a/ratis-test/src/test/java/org/apache/ratis/RaftLogTruncateTests.java 
b/ratis-test/src/test/java/org/apache/ratis/RaftLogTruncateTests.java
index 9ea78e47c..5c4753688 100644
--- a/ratis-test/src/test/java/org/apache/ratis/RaftLogTruncateTests.java
+++ b/ratis-test/src/test/java/org/apache/ratis/RaftLogTruncateTests.java
@@ -142,7 +142,7 @@ public abstract class RaftLogTruncateTests<CLUSTER extends 
MiniRaftCluster> exte
       for (RaftPeer peer : cluster.getGroup().getPeers()) {
         final RaftServer.Division division = cluster.getDivision(peer.getId());
         assertLogEntries(division, oldLeaderTerm, firstBatch);
-        assertEmptyTransactionContextMap(division);
+        waitForEmptyTransactionContextMap(division);
       }
 
       // kill a majority of followers
@@ -221,15 +221,12 @@ public abstract class RaftLogTruncateTests<CLUSTER 
extends MiniRaftCluster> exte
     for (RaftPeer peer : cluster.getGroup().getPeers()) {
       final RaftServer.Division division = cluster.getDivision(peer.getId());
       assertLogEntries(division, oldLeaderTerm, expectedMessages);
-      final String name = "assertEmptyTransactionContextMap:" + 
division.getId();
-      JavaUtils.attempt(() -> assertEmptyTransactionContextMap(division),
-          10, HUNDRED_MILLIS, name, LOG);
-
-  }
+      waitForEmptyTransactionContextMap(division);
+    }
 
     if (!exceptions.isEmpty()) {
       LOG.info("{} exceptions", exceptions.size());
-      for(int i = 0 ; i < exceptions.size(); i++) {
+      for (int i = 0; i < exceptions.size(); i++) {
         LOG.info("exception {})", i, exceptions.get(i));
       }
       Assertions.fail();
@@ -241,6 +238,11 @@ public abstract class RaftLogTruncateTests<CLUSTER extends 
MiniRaftCluster> exte
     Assertions.assertTrue(map.isEmpty(), () -> d.getId() + " 
TransactionContextMap is non-empty: " + map);
   }
 
+  void waitForEmptyTransactionContextMap(RaftServer.Division d) throws 
InterruptedException {
+    final String name = "assertEmptyTransactionContextMap:" + d.getId();
+    JavaUtils.attempt(() -> assertEmptyTransactionContextMap(d), 10, 
HUNDRED_MILLIS, name, LOG);
+  }
+
   static void assertEntriesInTransactionContextMap(RaftServer.Division 
division,
       SimpleMessage[] existing, SimpleMessage[] nonExisting) {
     final RaftLog log = division.getRaftLog();
diff --git 
a/ratis-test/src/test/java/org/apache/ratis/grpc/TestLinearizableReadRepliedIndexWithGrpc.java
 
b/ratis-test/src/test/java/org/apache/ratis/grpc/TestLinearizableReadRepliedIndexWithGrpc.java
index f08346fc0..795257d1f 100644
--- 
a/ratis-test/src/test/java/org/apache/ratis/grpc/TestLinearizableReadRepliedIndexWithGrpc.java
+++ 
b/ratis-test/src/test/java/org/apache/ratis/grpc/TestLinearizableReadRepliedIndexWithGrpc.java
@@ -26,6 +26,7 @@ import 
org.apache.ratis.server.RaftServerConfigKeys.Read.ReadIndex.Type;
 import org.apache.ratis.server.impl.MiniRaftCluster;
 import org.apache.ratis.server.impl.ReplyFlusher;
 import org.apache.ratis.util.CodeInjectionForTesting;
+import org.apache.ratis.util.JavaUtils;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
@@ -89,11 +90,9 @@ public class TestLinearizableReadRepliedIndexWithGrpc
         f0Replies.add(new Reply(0, f0Client.async().sendReadOnly(QUERY, f0)));
         f1Replies.add(new Reply(0, f1Client.async().sendReadOnly(QUERY, f1)));
 
-        // sleep in order to make sure
-        // (1) the count is incremented, and
-        // (2) the reads will wait for the repliedIndex.
-        Thread.sleep(100);
-        assertEquals(count, leaderStateMachine.getCount());
+        // Wait until the leader state machine has applied the write while the 
ReplyFlusher remains blocked.
+        JavaUtils.attempt(() -> assertEquals(count, 
leaderStateMachine.getCount()),
+            10, HUNDRED_MILLIS, "leaderStateMachine count " + count, null);
       }
 
       for (int i = 0; i < n; i++) {
diff --git 
a/ratis-test/src/test/java/org/apache/ratis/shell/cli/sh/ElectionCommandIntegrationTest.java
 
b/ratis-test/src/test/java/org/apache/ratis/shell/cli/sh/ElectionCommandIntegrationTest.java
index a61bb19f7..4a7ec0ef0 100644
--- 
a/ratis-test/src/test/java/org/apache/ratis/shell/cli/sh/ElectionCommandIntegrationTest.java
+++ 
b/ratis-test/src/test/java/org/apache/ratis/shell/cli/sh/ElectionCommandIntegrationTest.java
@@ -150,13 +150,16 @@ public abstract class ElectionCommandIntegrationTest 
<CLUSTER extends MiniRaftCl
   void runTestElectionStepDownCommand(MiniRaftCluster cluster) throws 
Exception {
     final RaftServer.Division leader = RaftTestUtil.waitForLeader(cluster);
     String sb = getClusterAddress(cluster);
-    RaftServer.Division newLeader = cluster.getFollowers().get(0);
     final StringPrintStream out = new StringPrintStream();
     RatisShell shell = new RatisShell(out.getPrintStream());
-    Assertions.assertNotEquals(cluster.getLeader().getId(), newLeader.getId());
     Assertions.assertEquals(2, cluster.getFollowers().size());
-    int ret = shell.run("election", "stepDown", "-peers", sb.toString());
+    int ret = shell.run("election", "pause", "-peers", sb, "-address",
+        leader.getPeer().getAddress());
+    Assertions.assertEquals(0, ret);
+
+    ret = shell.run("election", "stepDown", "-peers", sb);
     Assertions.assertEquals(0, ret);
-    Assertions.assertEquals(3, cluster.getFollowers().size());
+    JavaUtils.attempt(() -> Assertions.assertNotEquals(leader.getId(), 
RaftTestUtil.waitForLeader(cluster).getId()),
+        10, TimeDuration.valueOf(1, TimeUnit.SECONDS), 
"testElectionStepDownCommand", LOG);
   }
 }

Reply via email to