@@ -336,7 +336,9 @@ private void becomeLeader(final long epoch) {
336336 configManager .getLoadManager ().startTopologyService ();
337337 }
338338
339- // Start the remaining leader services in parallel and wait for all of them to finish.
339+ // Start the remaining leader services in parallel and wait for all of them to finish. Each
340+ // startup swallows and logs its own failure (see startInParallelIfEpochCurrent), so a single
341+ // misbehaving service cannot abort the whole transition and leave the node stuck warming up.
340342 final CompletableFuture <?>[] startups =
341343 leaderServiceStartups ().stream ()
342344 .map (startup -> startInParallelIfEpochCurrent (epoch , startup ))
@@ -364,31 +366,47 @@ private void becomeLeader(final long epoch) {
364366 }
365367
366368 /** The leader services that can be started independently, in parallel, within one epoch. */
367- private List <Runnable > leaderServiceStartups () {
369+ private List <LeaderServiceStartup > leaderServiceStartups () {
368370 return Arrays .asList (
369- () -> configManager .getProcedureManager ().getStore ().getProcedureInfo ().upgrade (),
370- () -> configManager .getRetryFailedTasksThread ().startRetryFailedTasksService (),
371- () -> configManager .getPartitionManager ().startRegionCleaner (),
371+ new LeaderServiceStartup (
372+ "ProcedureInfo.upgrade" ,
373+ () -> configManager .getProcedureManager ().getStore ().getProcedureInfo ().upgrade ()),
374+ new LeaderServiceStartup (
375+ "RetryFailedTasksService" ,
376+ () -> configManager .getRetryFailedTasksThread ().startRetryFailedTasksService ()),
377+ new LeaderServiceStartup (
378+ "RegionCleaner" , () -> configManager .getPartitionManager ().startRegionCleaner ()),
372379 // Add metrics after leader ready.
373- ( ) -> configManager .addMetrics (),
380+ new LeaderServiceStartup ( "Metrics" , ( ) -> configManager .addMetrics () ),
374381 // Activate leader related service for config pipe.
375- () -> PipeConfigNodeAgent .runtime ().notifyLeaderReady (),
382+ new LeaderServiceStartup (
383+ "PipeConfigNodeRuntime" , () -> PipeConfigNodeAgent .runtime ().notifyLeaderReady ()),
376384 // CQ recovery may be time-consuming, so it is just one more parallel startup.
377- () -> configManager .getCQManager ().startCQScheduler (),
378- () -> configManager .getPipeManager ().getPipeRuntimeCoordinator ().startPipeMetaSync (),
379- () -> configManager .getPipeManager ().getPipeRuntimeCoordinator ().startPipeHeartbeat (),
380- () ->
381- configManager
382- .getPipeManager ()
383- .getPipeRuntimeCoordinator ()
384- .onConfigRegionGroupLeaderChanged (),
385- () ->
386- configManager
387- .getSubscriptionManager ()
388- .getSubscriptionCoordinator ()
389- .startSubscriptionMetaSync (),
385+ new LeaderServiceStartup (
386+ "CQScheduler" , () -> configManager .getCQManager ().startCQScheduler ()),
387+ new LeaderServiceStartup (
388+ "PipeMetaSync" ,
389+ () -> configManager .getPipeManager ().getPipeRuntimeCoordinator ().startPipeMetaSync ()),
390+ new LeaderServiceStartup (
391+ "PipeHeartbeat" ,
392+ () -> configManager .getPipeManager ().getPipeRuntimeCoordinator ().startPipeHeartbeat ()),
393+ new LeaderServiceStartup (
394+ "PipeOnLeaderChanged" ,
395+ () ->
396+ configManager
397+ .getPipeManager ()
398+ .getPipeRuntimeCoordinator ()
399+ .onConfigRegionGroupLeaderChanged ()),
400+ new LeaderServiceStartup (
401+ "SubscriptionMetaSync" ,
402+ () ->
403+ configManager
404+ .getSubscriptionManager ()
405+ .getSubscriptionCoordinator ()
406+ .startSubscriptionMetaSync ()),
390407 // To adapt old version, we check cluster ID after state machine has been fully recovered.
391- () -> configManager .getClusterManager ().checkClusterId ());
408+ new LeaderServiceStartup (
409+ "CheckClusterId" , () -> configManager .getClusterManager ().checkClusterId ()));
392410 }
393411
394412 /** Tear down every leader service. Runs on the single transition thread. */
@@ -422,16 +440,35 @@ private void stopLeaderServices() {
422440 }
423441
424442 /**
425- * Run {@code startup} on {@link #leaderServicesStartupPool}, skipping it (and recording success)
426- * if the epoch has gone stale by the time it is picked up. Returns a future that always completes
427- * normally so {@link CompletableFuture#allOf} acts as a clean join barrier.
443+ * Run {@code startup} on {@link #leaderServicesStartupPool}, skipping it if the epoch has gone
444+ * stale by the time it is picked up. Any {@link RuntimeException} thrown by the startup is caught
445+ * and logged here instead of being allowed to escape: this keeps one misbehaving service from
446+ * failing the {@link CompletableFuture#allOf} join barrier in {@link #becomeLeader}, which would
447+ * otherwise abort the whole transition before {@link #markLeaderServicesReadyIfEpochCurrent} runs
448+ * and leave the node stuck returning {@code CONFIG_NODE_LEADER_WARMING_UP} forever. The returned
449+ * future therefore always completes normally, so {@code allOf} acts as a clean join barrier.
428450 */
429451 private CompletableFuture <Void > startInParallelIfEpochCurrent (
430- final long epoch , final Runnable startup ) {
452+ final long epoch , final LeaderServiceStartup startup ) {
431453 return CompletableFuture .runAsync (
432454 () -> {
433- if (isCurrentLeaderServicesEpoch (epoch )) {
455+ if (!isCurrentLeaderServicesEpoch (epoch )) {
456+ return ;
457+ }
458+ try {
434459 startup .run ();
460+ } catch (final Exception e ) {
461+ // Swallow and log so a single failed startup cannot stall leader warm-up. The service
462+ // stays unstarted, but the node still finishes warming up and begins serving; the
463+ // failure is observable through this error log.
464+ LOGGER .error (
465+ "Current ConfigNode(nodeId: {}, ip: {}) failed to start leader service [{}], the"
466+ + " node will still finish warming up; this service stays unavailable until the"
467+ + " next leadership transition." ,
468+ ConfigNodeDescriptor .getInstance ().getConf ().getConfigNodeId (),
469+ currentNodeTEndPoint ,
470+ startup .name (),
471+ e );
435472 }
436473 },
437474 leaderServicesStartupPool );
@@ -687,4 +724,27 @@ private static long parseEndIndex(String filename) {
687724 }
688725 return Long .parseLong (endIndexString );
689726 }
727+
728+ /**
729+ * A single leader service startup paired with a human-readable name, so a failure can be logged
730+ * against the service that produced it (see {@link #startInParallelIfEpochCurrent}).
731+ */
732+ private static class LeaderServiceStartup {
733+
734+ private final String name ;
735+ private final Runnable startup ;
736+
737+ private LeaderServiceStartup (final String name , final Runnable startup ) {
738+ this .name = name ;
739+ this .startup = startup ;
740+ }
741+
742+ private String name () {
743+ return name ;
744+ }
745+
746+ private void run () {
747+ startup .run ();
748+ }
749+ }
690750}
0 commit comments