yew1eb commented on code in PR #3838:
URL: https://github.com/apache/celeborn/pull/3838#discussion_r3967855208


##########
master/src/main/java/org/apache/celeborn/service/deploy/master/clustermeta/ha/HARaftServer.java:
##########
@@ -101,9 +101,16 @@ public class HARaftServer {
 
   private final CelebornConf conf;
   private final AtomicBoolean stopped = new AtomicBoolean(false);
+  private final AtomicBoolean unexpectedCloseHandled = new 
AtomicBoolean(false);
   private long workerTimeoutDeadline;
   private long appTimeoutDeadline;
 
+  private Runnable unexpectedCloseHandler =
+      () -> {
+        LOG.error("Exiting master process because the raft server was closed 
unexpectedly.");
+        System.exit(1);
+      };
+

Review Comment:
   Fail-fast here is intentional: once the embedded RaftServer is closed by 
Ratis (e.g. JvmPauseMonitor after a long pause), the master can no longer serve 
any write and silently drops out of the quorum while keeping its RPC/HTTP ports 
open. Exiting the process makes the failure explicit so external supervision 
(systemd/K8s) can restart the node and let it rejoin — similar to how HDFS 
NameNode terminates on fatal errors. The handler is already extracted as a 
replaceable Runnable for testability, and users who prefer to keep the process 
alive can set celeborn.master.ha.ratis.unexpected.close.action=none. The error 
log already includes the detected lifecycle state as context.



##########
master/src/main/java/org/apache/celeborn/service/deploy/master/clustermeta/ha/HARaftServer.java:
##########
@@ -311,6 +319,34 @@ public void stop(boolean transferLeadership) {
     }
   }
 
+  private void checkRaftServerState() {
+    if (stopped.get() || unexpectedCloseHandled.get()) {
+      return;
+    }
+    LifeCycle.State state = server.getLifeCycleState();
+    if ((state == LifeCycle.State.CLOSED || state == LifeCycle.State.EXCEPTION)
+        && unexpectedCloseHandled.compareAndSet(false, true)) {
+      LOG.error(
+          "Raft server {} was closed unexpectedly (state {}), e.g. by the 
Ratis "
+              + "JvmPauseMonitor after a long JVM pause. Action: {}.",
+          server.getId(),
+          state,
+          conf.haMasterRatisUnexpectedCloseAction());
+      onUnexpectedRaftServerClose();
+    }
+  }

Review Comment:
   Good catch — the description was broader than the implementation. We've 
updated the PR description to match: the check triggers on terminal lifecycle 
states (CLOSED / EXCEPTION). We intentionally do not broaden to state != 
RUNNING (transitional states like STARTING/CLOSING would misfire) nor add a 
group-membership check, which would cost a getGroupInfo RPC on every role-check 
interval.



##########
master/src/main/java/org/apache/celeborn/service/deploy/master/clustermeta/ha/HARaftServer.java:
##########
@@ -540,6 +576,19 @@ public Optional<LeaderPeerEndpoints> 
getCachedLeaderPeerRpcEndpoint() {
    * Get the group info (peer role and leader peer id) from Ratis server and 
update the server role.
    */
   public void updateServerRole() {
+    LifeCycle.State state = server.getLifeCycleState();
+    if (state != LifeCycle.State.RUNNING) {
+      // Only log on role change to avoid flooding logs on every metrics 
scrape.
+      if (cachedPeerRole.isPresent()) {
+        LOG.warn(
+            "Raft server {} is not RUNNING (state {}). Setting cached role to 
UNRECOGNIZED "
+                + "and resetting leader info.",
+            server.getId(),
+            state);
+      }
+      setServerRole(null, null, null);
+      return;
+    }

Review Comment:
   Fixed — the reset now only applies to terminal states (CLOSED / EXCEPTION); 
transitional states keep the previous code path. Thanks!



##########
master/src/test/java/org/apache/celeborn/service/deploy/master/clustermeta/ha/RatisMasterStatusSystemSuiteJ.java:
##########
@@ -2145,4 +2147,31 @@ public void testRegisterApplicationInfo() {
     assertEquals(statusSystem.applicationInfos.get(appId).userIdentifier(), 
userIdentifier);
     assertEquals(statusSystem.applicationInfos.get(appId).extraInfo(), 
extraInfo);
   }
+
+  @Test
+  public void testUnexpectedRaftServerClose() throws Exception {
+    HARaftServer nonLeader =
+        !RATISSERVER1.isLeader()
+            ? RATISSERVER1
+            : (!RATISSERVER2.isLeader() ? RATISSERVER2 : RATISSERVER3);
+    try {
+      AtomicBoolean handlerInvoked = new AtomicBoolean(false);
+      nonLeader.setUnexpectedCloseHandler(() -> handlerInvoked.set(true));
+
+      // Simulate the Ratis JvmPauseMonitor closing the local raft server.
+      nonLeader.getServer().close();
+      Assert.assertEquals(LifeCycle.State.CLOSED, nonLeader.getServerState());
+
+      long deadline = System.currentTimeMillis() + 30_000;
+      while (!handlerInvoked.get() && System.currentTimeMillis() < deadline) {
+        Thread.sleep(200L);
+      }
+      Assert.assertTrue(handlerInvoked.get());

Review Comment:
   Fixed — checkRaftServerState() is now package-visible with 
@VisibleForTesting, and the test invokes it once directly after closing the 
server instead of polling the scheduler. Deterministic and much faster.



##########
master/src/main/java/org/apache/celeborn/service/deploy/master/clustermeta/ha/HARaftServer.java:
##########
@@ -311,6 +319,34 @@ public void stop(boolean transferLeadership) {
     }
   }
 
+  private void checkRaftServerState() {
+    if (stopped.get() || unexpectedCloseHandled.get()) {
+      return;
+    }
+    LifeCycle.State state = server.getLifeCycleState();
+    if ((state == LifeCycle.State.CLOSED || state == LifeCycle.State.EXCEPTION)
+        && unexpectedCloseHandled.compareAndSet(false, true)) {
+      LOG.error(
+          "Raft server {} was closed unexpectedly (state {}), e.g. by the 
Ratis "
+              + "JvmPauseMonitor after a long JVM pause. Action: {}.",
+          server.getId(),
+          state,
+          conf.haMasterRatisUnexpectedCloseAction());
+      onUnexpectedRaftServerClose();
+    }
+  }
+
+  void onUnexpectedRaftServerClose() {
+    if ("exit".equals(conf.haMasterRatisUnexpectedCloseAction())) {
+      unexpectedCloseHandler.run();
+    }
+  }
+
+  @VisibleForTesting
+  void setUnexpectedCloseHandler(Runnable handler) {
+    this.unexpectedCloseHandler = handler;

Review Comment:
   Fixed — the setter now rejects null via Objects.requireNonNull.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to