smengcl commented on code in PR #10999:
URL: https://github.com/apache/ozone/pull/10999#discussion_r4212560775


##########
hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMStateMachine.java:
##########
@@ -414,18 +443,77 @@ public void notifyTermIndexUpdated(long term, long index) 
{
    * container reports are processed against the up-to-date container/pipeline
    * state rather than a stale, mid-replay snapshot.
    */
-  private void tryStartDNServerAndRefreshSafeMode() {
-    if (isStateMachineReady.get()) {
+  private synchronized void tryStartDNServerAndRefreshSafeMode() {
+    if (scm.isStopped() || isStateMachineReady.get()) {
       return;
     }
     if (scm.getScmContext().isLeader() || isFollowerCaughtUp()) {
       if (isStateMachineReady.compareAndSet(false, true)) {
+        // No further retry is needed. shutdown() lets the current retry, if
+        // this method was called by it, finish normally without cancellation.
+        dnServerStartRetryExecutor.shutdown();
         scm.getDatanodeProtocolServer().start();
         scm.getScmSafeModeManager().refreshAndValidate();
       }
     }
   }
 
+  /**
+   * Periodic fallback for the deferred datanode-server start on a follower. 
The
+   * Ratis callbacks ({@code applyTransaction}, {@code notifyTermIndexUpdated},
+   * {@code notifyLeaderChanged}) only fire on new activity, so on an idle
+   * cluster a restarted follower can miss the moment the leader's committed
+   * index becomes observable (it is not yet known when {@code 
notifyLeaderChanged}
+   * fires) and never start its datanode server, leaving it stuck in safe mode.
+   * This re-checks until the server starts. Starting stays
+   * gated by the same {@link #tryStartDNServerAndRefreshSafeMode()} 
predicate, so
+   * it cannot start the server before genuine catch-up.
+   */
+  private void retryStartDNServerUntilReady() {
+    try {
+      if (!scm.isStopped() && !isStateMachineReady.get()) {
+        tryStartDNServerAndRefreshSafeMode();
+      }
+    } catch (Exception e) {
+      // Do not let a failed readiness check terminate the fallback. The
+      // finally block only schedules another attempt if startup is pending.
+      LOG.warn("Deferred datanode-server start retry failed", e);
+    } finally {
+      synchronized (this) {
+        dnServerStartRetryFuture = null;
+        scheduleDNServerStartRetry();
+      }
+    }
+  }
+
+  private synchronized void scheduleDNServerStartRetry() {
+    if (scm.isStopped() || isStateMachineReady.get() ||
+        dnServerStartRetryExecutor.isShutdown() || dnServerStartRetryFuture != 
null) {
+      return;
+    }
+    dnServerStartRetryFuture = dnServerStartRetryExecutor.schedule(
+        this::retryStartDNServerUntilReady,

Review Comment:
   Addressed in a3eeb68d8ba.
   
   The readiness check compares against a captured commit index, not the 
leader's continuously advancing index, so a busy leader does not create a 
moving catch-up target. Added 
`testRetryUsesCapturedCommitIndexWhenLeaderAdvances` to verify this.
   
   A genuinely stalled follower can still wait indefinitely. Continuing retries 
lets it recover when progress resumes; a retry cutoff would leave the datanode 
server permanently deferred. Added WARN logging after 30 seconds and then every 
five minutes, including attempt count, elapsed time, `lastAppliedIndex` and 
`leaderCommitIndexOnStart`. Tests cover warning throttling, unavailable commit 
information, and stopping after readiness or shutdown.



##########
hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMStateMachine.java:
##########
@@ -111,12 +133,18 @@ public SCMStateMachine(final StorageContainerManager scm,
             .setNameFormat(scm.threadNamePrefix() + "SCMInstallSnapshot-%d")
             .build()
     );
+    this.dnServerStartRetryExecutor = HadoopExecutors.newScheduledThreadPool(1,
+        new ThreadFactoryBuilder()
+            .setNameFormat(scm.threadNamePrefix() + "SCMDNServerStartRetry-%d")
+            .setDaemon(true)
+            .build());
     isInitialized = true;
   }
 
   public SCMStateMachine() {

Review Comment:
   Addressed in a3eeb68d8ba.
   
   Yes, intentionally: the no-argument constructor is used only by 
`SCMRatisServerImpl.initialize()` to bootstrap Ratis storage, without an SCM 
instance or background executors. Added a comment explaining this and made 
`stopDNServerStartRetry()` safely skip bootstrap instances, consistent with the 
existing callback and `close()` guards.
   
   Added `testBootstrapStateMachineSkipsSCMCallbacksAndRetryStop`, covering 
bootstrap callbacks, repeated stop calls and `close()`, without allocating 
retry threads.



-- 
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]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to