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 d7370f897 RATIS-2378. fix listener role transition (#1331)
d7370f897 is described below

commit d7370f897f43aa31d44beb3bf61933430bfb8355
Author: jiangyuan <[email protected]>
AuthorDate: Sun Jan 4 05:02:01 2026 +0800

    RATIS-2378. fix listener role transition (#1331)
---
 .../apache/ratis/server/impl/RaftServerImpl.java    | 12 ++++++++++--
 .../org/apache/ratis/server/impl/ServerState.java   | 21 ++++++++++++++++-----
 .../server/impl/SnapshotInstallationHandler.java    |  3 ++-
 .../ratis/server/impl/LeaderElectionTests.java      |  4 ++++
 4 files changed, 32 insertions(+), 8 deletions(-)

diff --git 
a/ratis-server/src/main/java/org/apache/ratis/server/impl/RaftServerImpl.java 
b/ratis-server/src/main/java/org/apache/ratis/server/impl/RaftServerImpl.java
index 689bb8cef..60f72e001 100644
--- 
a/ratis-server/src/main/java/org/apache/ratis/server/impl/RaftServerImpl.java
+++ 
b/ratis-server/src/main/java/org/apache/ratis/server/impl/RaftServerImpl.java
@@ -591,7 +591,7 @@ class RaftServerImpl implements RaftServer.Division,
       throw new IllegalStateException("Unexpected role " + old);
     }
     CompletableFuture<Void> future = CompletableFuture.completedFuture(null);
-    if ((old != RaftPeerRole.FOLLOWER || force) && old != 
RaftPeerRole.LISTENER) {
+    if (shouldSetFollower(old, force)) {
       setRole(RaftPeerRole.FOLLOWER, reason);
       if (old == RaftPeerRole.LEADER) {
         future = role.shutdownLeaderState(false)
@@ -607,7 +607,7 @@ class RaftServerImpl implements RaftServer.Division,
         state.setLeader(null, reason);
       } else if (old == RaftPeerRole.CANDIDATE) {
         future = role.shutdownLeaderElection();
-      } else if (old == RaftPeerRole.FOLLOWER) {
+      } else if (old == RaftPeerRole.FOLLOWER || old == RaftPeerRole.LISTENER) 
{
         future = role.shutdownFollowerState();
       }
 
@@ -620,6 +620,14 @@ class RaftServerImpl implements RaftServer.Division,
     return future;
   }
 
+    private boolean shouldSetFollower(RaftPeerRole old, boolean force) {
+      if (old == RaftPeerRole.LISTENER) {
+        final RaftConfigurationImpl conf = state.getRaftConf();
+        return conf.isStable() && conf.containsInConf(getId());
+      }
+      return old != RaftPeerRole.FOLLOWER || force;
+    }
+
   synchronized CompletableFuture<Void> changeToFollowerAndPersistMetadata(
       long newTerm,
       boolean allowListener,
diff --git 
a/ratis-server/src/main/java/org/apache/ratis/server/impl/ServerState.java 
b/ratis-server/src/main/java/org/apache/ratis/server/impl/ServerState.java
index ee1b7d37b..bcf11baf7 100644
--- a/ratis-server/src/main/java/org/apache/ratis/server/impl/ServerState.java
+++ b/ratis-server/src/main/java/org/apache/ratis/server/impl/ServerState.java
@@ -376,10 +376,12 @@ class ServerState {
     return getLog().getLastCommittedIndex() >= 
getRaftConf().getLogEntryIndex();
   }
 
-  void setRaftConf(LogEntryProto entry) {
+  private boolean setRaftConf(LogEntryProto entry) {
     if (entry.hasConfigurationEntry()) {
       setRaftConf(LogProtoUtils.toRaftConfiguration(entry));
+      return true;
     }
+    return false;
   }
 
   void setRaftConf(RaftConfiguration conf) {
@@ -397,10 +399,19 @@ class ServerState {
     configurationManager.removeConfigurations(logIndex);
   }
 
-  void updateConfiguration(List<LogEntryProto> entries) {
-    if (entries != null && !entries.isEmpty()) {
-      configurationManager.removeConfigurations(entries.get(0).getIndex());
-      entries.forEach(this::setRaftConf);
+  void updateConfiguration(List<LogEntryProto> entries) throws IOException {
+    if (entries == null || entries.isEmpty()) {
+      return;
+    }
+    configurationManager.removeConfigurations(entries.get(0).getIndex());
+
+    boolean changed = false;
+    for(LogEntryProto entry : entries) {
+      changed |= setRaftConf(entry);
+    }
+
+    if (changed && server.getRole().getCurrentRole() == RaftPeerRole.LISTENER) 
{
+      server.changeToFollowerAndPersistMetadata(getCurrentTerm(), true, 
"setRaftConf").join();
     }
   }
 
diff --git 
a/ratis-server/src/main/java/org/apache/ratis/server/impl/SnapshotInstallationHandler.java
 
b/ratis-server/src/main/java/org/apache/ratis/server/impl/SnapshotInstallationHandler.java
index eac690feb..46b6aaf87 100644
--- 
a/ratis-server/src/main/java/org/apache/ratis/server/impl/SnapshotInstallationHandler.java
+++ 
b/ratis-server/src/main/java/org/apache/ratis/server/impl/SnapshotInstallationHandler.java
@@ -46,6 +46,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
+import java.util.Collections;
 import java.util.Optional;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -145,7 +146,7 @@ class SnapshotInstallationHandler {
         state.truncate(proto.getIndex());
         if 
(!state.getRaftConf().equals(LogProtoUtils.toRaftConfiguration(proto))) {
           LOG.info("{}: set new configuration {} from snapshot", 
getMemberId(), ProtoUtils.shortDebugString(proto));
-          state.setRaftConf(proto);
+          state.updateConfiguration(Collections.singletonList(proto));
           state.writeRaftConfiguration(proto);
           server.getStateMachine().event().notifyConfigurationChanged(
               proto.getTerm(), proto.getIndex(), 
proto.getConfigurationEntry());
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 456d2ad2a..6959bd342 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
@@ -556,6 +556,10 @@ public abstract class LeaderElectionTests<CLUSTER extends 
MiniRaftCluster>
         assertTrue(reply.isSuccess());
         Collection<RaftPeer> peer = 
leader.getRaftConf().getAllPeers(RaftProtos.RaftPeerRole.LISTENER);
         assertEquals(0, peer.size());
+
+        listeners = cluster.getListeners()
+                  
.stream().map(RaftServer.Division::getPeer).collect(Collectors.toList());
+        assertEquals(0, listeners.size());
       }
       cluster.shutdown();
     }

Reply via email to