Skip to content

Commit 72f7c8e

Browse files
CRZbulabulaJackieTien97
authored andcommitted
Catch per-startup failures during ConfigNode leader warm-up (#17898)
1 parent 4b9b879 commit 72f7c8e

1 file changed

Lines changed: 86 additions & 26 deletions

File tree

iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/consensus/statemachine/ConfigRegionStateMachine.java

Lines changed: 86 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)