lifecycleion 0.0.13 → 0.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/lib/lifecycle-manager/index.cjs +642 -327
- package/dist/lib/lifecycle-manager/index.cjs.map +1 -1
- package/dist/lib/lifecycle-manager/index.d.cts +98 -23
- package/dist/lib/lifecycle-manager/index.d.ts +98 -23
- package/dist/lib/lifecycle-manager/index.js +642 -327
- package/dist/lib/lifecycle-manager/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1263,6 +1263,16 @@ var LifecycleManagerEvents = class {
|
|
|
1263
1263
|
code: info?.code
|
|
1264
1264
|
});
|
|
1265
1265
|
}
|
|
1266
|
+
componentStalledResolved(name, stallInfo, stalledDurationMS) {
|
|
1267
|
+
this.emit("component:stalled-resolved", {
|
|
1268
|
+
name,
|
|
1269
|
+
stallInfo,
|
|
1270
|
+
stalledDurationMS
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
1273
|
+
componentUnexpectedStop(name, error) {
|
|
1274
|
+
this.emit("component:unexpected-stop", { name, error });
|
|
1275
|
+
}
|
|
1266
1276
|
componentShutdownForceCompleted(name) {
|
|
1267
1277
|
this.emit("component:shutdown-force-completed", { name });
|
|
1268
1278
|
}
|
|
@@ -1449,6 +1459,25 @@ var lifecycleManagerErrCodes = {
|
|
|
1449
1459
|
StopTimeout: "StopTimeout"
|
|
1450
1460
|
};
|
|
1451
1461
|
|
|
1462
|
+
// src/lib/lifecycle-manager/constants.ts
|
|
1463
|
+
var LIFECYCLE_MANAGER_MESSAGE_BULK_OPERATION_IN_PROGRESS = "Cannot unregister during bulk operation";
|
|
1464
|
+
var LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_FOUND = "Component not found";
|
|
1465
|
+
var LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_RUNNING = "Component not running";
|
|
1466
|
+
var LIFECYCLE_MANAGER_MESSAGE_COMPONENT_STALLED = "Component is stalled";
|
|
1467
|
+
var LIFECYCLE_MANAGER_MESSAGE_BULK_STARTUP_IN_PROGRESS = "Bulk startup in progress";
|
|
1468
|
+
var LIFECYCLE_MANAGER_MESSAGE_SHUTDOWN_IN_PROGRESS = "Shutdown in progress";
|
|
1469
|
+
var LIFECYCLE_MANAGER_MESSAGE_UNKNOWN_ERROR = "Unknown error";
|
|
1470
|
+
var LIFECYCLE_MANAGER_LOG_AUTO_DETACH_LAST_COMPONENT_STOP = "Auto-detaching process signals on last component stop";
|
|
1471
|
+
var LIFECYCLE_MANAGER_LOG_LOGGER_EXIT_DURING_SHUTDOWN = "Logger exit called during shutdown, waiting...";
|
|
1472
|
+
var LIFECYCLE_MANAGER_LOG_MESSAGE_HANDLER_FAILED = "Message handler failed: {{error.message}}";
|
|
1473
|
+
var LIFECYCLE_MANAGER_MESSAGE_GRACEFUL_SHUTDOWN_TIMED_OUT = "Graceful shutdown timed out";
|
|
1474
|
+
var LIFECYCLE_MANAGER_MESSAGE_FORCE_SHUTDOWN_TIMED_OUT = "Force shutdown timed out";
|
|
1475
|
+
var LIFECYCLE_MANAGER_LOG_OPTIONAL_COMPONENT_UNEXPECTED_STOP_DURING_STARTUP = "Optional component stopped unexpectedly during startup, continuing: {{error.message}}";
|
|
1476
|
+
var LIFECYCLE_MANAGER_LOG_REQUIRED_COMPONENT_UNEXPECTED_STOP_DURING_STARTUP = "Required component stopped unexpectedly during startup: {{error.message}}";
|
|
1477
|
+
var LIFECYCLE_MANAGER_MESSAGE_REGISTER_SHUTDOWN_IN_PROGRESS = "Cannot register component while shutdown is in progress (isShuttingDown=true).";
|
|
1478
|
+
var LIFECYCLE_MANAGER_MESSAGE_REGISTER_REQUIRED_DEPENDENCY_DURING_STARTUP = "Cannot register component during startup when it is a required dependency for other components.";
|
|
1479
|
+
var LIFECYCLE_MANAGER_MESSAGE_DUPLICATE_COMPONENT_INSTANCE = "Component instance is already registered.";
|
|
1480
|
+
|
|
1452
1481
|
// src/lib/process-signal-manager.ts
|
|
1453
1482
|
var import_ulid = require("ulid");
|
|
1454
1483
|
var import_readline = __toESM(require("readline"), 1);
|
|
@@ -1993,8 +2022,15 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
1993
2022
|
componentTimestamps = /* @__PURE__ */ new Map();
|
|
1994
2023
|
componentErrors = /* @__PURE__ */ new Map();
|
|
1995
2024
|
componentStartAttemptTokens = /* @__PURE__ */ new Map();
|
|
2025
|
+
// Use per-stop ULIDs instead of incrementing counters because a stalled
|
|
2026
|
+
// component can be unregistered and replaced by a same-name instance before
|
|
2027
|
+
// the old floating stop promise settles.
|
|
2028
|
+
componentStopAttemptTokens = /* @__PURE__ */ new Map();
|
|
2029
|
+
pendingForceStopWaiters = /* @__PURE__ */ new Map();
|
|
2030
|
+
unexpectedStopsDuringStartup = /* @__PURE__ */ new Map();
|
|
1996
2031
|
// State flags
|
|
1997
2032
|
isStarting = false;
|
|
2033
|
+
autoAttachedSignalsDuringStartup = false;
|
|
1998
2034
|
isStarted = false;
|
|
1999
2035
|
isShuttingDown = false;
|
|
2000
2036
|
// Unique token used to detect shutdowns that happened during async start().
|
|
@@ -2143,7 +2179,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2143
2179
|
*/
|
|
2144
2180
|
async unregisterComponent(name, options) {
|
|
2145
2181
|
if (this.isStarting || this.isShuttingDown) {
|
|
2146
|
-
this.logger.entity(name).warn(
|
|
2182
|
+
this.logger.entity(name).warn(LIFECYCLE_MANAGER_MESSAGE_BULK_OPERATION_IN_PROGRESS, {
|
|
2147
2183
|
params: {
|
|
2148
2184
|
isStarting: this.isStarting,
|
|
2149
2185
|
isShuttingDown: this.isShuttingDown
|
|
@@ -2152,7 +2188,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2152
2188
|
return {
|
|
2153
2189
|
success: false,
|
|
2154
2190
|
componentName: name,
|
|
2155
|
-
reason:
|
|
2191
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_BULK_OPERATION_IN_PROGRESS,
|
|
2156
2192
|
code: "bulk_operation_in_progress",
|
|
2157
2193
|
wasStopped: false,
|
|
2158
2194
|
wasRegistered: this.hasComponent(name)
|
|
@@ -2160,11 +2196,11 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2160
2196
|
}
|
|
2161
2197
|
const component = this.getComponent(name);
|
|
2162
2198
|
if (!component) {
|
|
2163
|
-
this.logger.entity(name).warn(
|
|
2199
|
+
this.logger.entity(name).warn(LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_FOUND);
|
|
2164
2200
|
return {
|
|
2165
2201
|
success: false,
|
|
2166
2202
|
componentName: name,
|
|
2167
|
-
reason:
|
|
2203
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_FOUND,
|
|
2168
2204
|
code: "component_not_found",
|
|
2169
2205
|
wasStopped: false,
|
|
2170
2206
|
wasRegistered: false
|
|
@@ -2177,7 +2213,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2177
2213
|
return {
|
|
2178
2214
|
success: false,
|
|
2179
2215
|
componentName: name,
|
|
2180
|
-
reason:
|
|
2216
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_STALLED,
|
|
2181
2217
|
code: "stop_failed",
|
|
2182
2218
|
stopFailureReason: "stalled",
|
|
2183
2219
|
wasStopped: false,
|
|
@@ -2229,10 +2265,13 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2229
2265
|
wasStopped = true;
|
|
2230
2266
|
}
|
|
2231
2267
|
this.components = this.components.filter((c) => c.getName() !== name);
|
|
2268
|
+
component._clearUnexpectedStopHandler();
|
|
2232
2269
|
this.componentStates.delete(name);
|
|
2233
2270
|
this.componentTimestamps.delete(name);
|
|
2234
2271
|
this.componentErrors.delete(name);
|
|
2235
2272
|
this.componentStartAttemptTokens.delete(name);
|
|
2273
|
+
this.componentStopAttemptTokens.delete(name);
|
|
2274
|
+
this.pendingForceStopWaiters.delete(name);
|
|
2236
2275
|
this.stalledComponents.delete(name);
|
|
2237
2276
|
this.runningComponents.delete(name);
|
|
2238
2277
|
this.updateStartedFlag();
|
|
@@ -2579,7 +2618,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2579
2618
|
startedComponents: [],
|
|
2580
2619
|
failedOptionalComponents: [],
|
|
2581
2620
|
skippedDueToDependency: [],
|
|
2582
|
-
reason:
|
|
2621
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_SHUTDOWN_IN_PROGRESS,
|
|
2583
2622
|
code: "shutdown_in_progress",
|
|
2584
2623
|
durationMS: Date.now() - startTime
|
|
2585
2624
|
};
|
|
@@ -2639,6 +2678,8 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2639
2678
|
};
|
|
2640
2679
|
}
|
|
2641
2680
|
this.isStarting = true;
|
|
2681
|
+
this.autoAttachedSignalsDuringStartup = false;
|
|
2682
|
+
this.unexpectedStopsDuringStartup.clear();
|
|
2642
2683
|
this.resetRepeatedShutdownRequestState();
|
|
2643
2684
|
this.shutdownMethod = null;
|
|
2644
2685
|
this.lastShutdownResult = null;
|
|
@@ -2771,13 +2812,49 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2771
2812
|
error: result.error,
|
|
2772
2813
|
durationMS: Date.now() - startTime
|
|
2773
2814
|
};
|
|
2815
|
+
} else if (result.code === "component_unexpected_stop") {
|
|
2816
|
+
this.unexpectedStopsDuringStartup.delete(name);
|
|
2817
|
+
const error = result.error || new Error(
|
|
2818
|
+
result.reason || `Component "${name}" stopped unexpectedly`
|
|
2819
|
+
);
|
|
2820
|
+
if (component.isOptional()) {
|
|
2821
|
+
if (!failedOptionalComponents.some((entry) => entry.name === name)) {
|
|
2822
|
+
failedOptionalComponents.push({ name, error });
|
|
2823
|
+
}
|
|
2824
|
+
this.logger.entity(name).warn(
|
|
2825
|
+
LIFECYCLE_MANAGER_LOG_OPTIONAL_COMPONENT_UNEXPECTED_STOP_DURING_STARTUP,
|
|
2826
|
+
{
|
|
2827
|
+
params: { error }
|
|
2828
|
+
}
|
|
2829
|
+
);
|
|
2830
|
+
} else {
|
|
2831
|
+
this.logger.entity(name).error(
|
|
2832
|
+
LIFECYCLE_MANAGER_LOG_REQUIRED_COMPONENT_UNEXPECTED_STOP_DURING_STARTUP,
|
|
2833
|
+
{
|
|
2834
|
+
params: { error }
|
|
2835
|
+
}
|
|
2836
|
+
);
|
|
2837
|
+
await this.rollbackStartup(startedComponents);
|
|
2838
|
+
return {
|
|
2839
|
+
success: false,
|
|
2840
|
+
startedComponents: [],
|
|
2841
|
+
failedOptionalComponents,
|
|
2842
|
+
skippedDueToDependency: Array.from(skippedDueToDependency),
|
|
2843
|
+
reason: error.message,
|
|
2844
|
+
code: "component_unexpected_stop",
|
|
2845
|
+
error,
|
|
2846
|
+
durationMS: Date.now() - startTime
|
|
2847
|
+
};
|
|
2848
|
+
}
|
|
2774
2849
|
} else {
|
|
2775
2850
|
if (component.isOptional()) {
|
|
2776
2851
|
this.logger.entity(name).warn(
|
|
2777
2852
|
"Optional component failed to start, continuing: {{error.message}}",
|
|
2778
2853
|
{
|
|
2779
2854
|
params: {
|
|
2780
|
-
error: result.error || new Error(
|
|
2855
|
+
error: result.error || new Error(
|
|
2856
|
+
result.reason || LIFECYCLE_MANAGER_MESSAGE_UNKNOWN_ERROR
|
|
2857
|
+
)
|
|
2781
2858
|
}
|
|
2782
2859
|
}
|
|
2783
2860
|
);
|
|
@@ -2791,14 +2868,18 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2791
2868
|
}
|
|
2792
2869
|
failedOptionalComponents.push({
|
|
2793
2870
|
name,
|
|
2794
|
-
error: result.error || new Error(
|
|
2871
|
+
error: result.error || new Error(
|
|
2872
|
+
result.reason || LIFECYCLE_MANAGER_MESSAGE_UNKNOWN_ERROR
|
|
2873
|
+
)
|
|
2795
2874
|
});
|
|
2796
2875
|
} else {
|
|
2797
2876
|
this.logger.entity(name).error(
|
|
2798
2877
|
"Required component failed to start, rolling back: {{error.message}}",
|
|
2799
2878
|
{
|
|
2800
2879
|
params: {
|
|
2801
|
-
error: result.error || new Error(
|
|
2880
|
+
error: result.error || new Error(
|
|
2881
|
+
result.reason || LIFECYCLE_MANAGER_MESSAGE_UNKNOWN_ERROR
|
|
2882
|
+
)
|
|
2802
2883
|
}
|
|
2803
2884
|
}
|
|
2804
2885
|
);
|
|
@@ -2815,6 +2896,25 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2815
2896
|
};
|
|
2816
2897
|
}
|
|
2817
2898
|
}
|
|
2899
|
+
const unexpectedStopResult2 = this.consumeUnexpectedStopsDuringStartup(
|
|
2900
|
+
startedComponents,
|
|
2901
|
+
failedOptionalComponents
|
|
2902
|
+
);
|
|
2903
|
+
startedComponents.splice(0, startedComponents.length);
|
|
2904
|
+
startedComponents.push(...unexpectedStopResult2.startedComponents);
|
|
2905
|
+
if (unexpectedStopResult2.requiredFailure) {
|
|
2906
|
+
await this.rollbackStartup(startedComponents);
|
|
2907
|
+
return {
|
|
2908
|
+
success: false,
|
|
2909
|
+
startedComponents: [],
|
|
2910
|
+
failedOptionalComponents,
|
|
2911
|
+
skippedDueToDependency: Array.from(skippedDueToDependency),
|
|
2912
|
+
reason: unexpectedStopResult2.requiredFailure.error.message,
|
|
2913
|
+
code: "component_unexpected_stop",
|
|
2914
|
+
error: unexpectedStopResult2.requiredFailure.error,
|
|
2915
|
+
durationMS: Date.now() - startTime
|
|
2916
|
+
};
|
|
2917
|
+
}
|
|
2818
2918
|
}
|
|
2819
2919
|
if (hasTimedOut) {
|
|
2820
2920
|
const durationMS2 = Date.now() - startTime;
|
|
@@ -2838,6 +2938,25 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2838
2938
|
code: "startup_timeout"
|
|
2839
2939
|
};
|
|
2840
2940
|
}
|
|
2941
|
+
const unexpectedStopResult = this.consumeUnexpectedStopsDuringStartup(
|
|
2942
|
+
startedComponents,
|
|
2943
|
+
failedOptionalComponents
|
|
2944
|
+
);
|
|
2945
|
+
startedComponents.splice(0, startedComponents.length);
|
|
2946
|
+
startedComponents.push(...unexpectedStopResult.startedComponents);
|
|
2947
|
+
if (unexpectedStopResult.requiredFailure) {
|
|
2948
|
+
await this.rollbackStartup(startedComponents);
|
|
2949
|
+
return {
|
|
2950
|
+
success: false,
|
|
2951
|
+
startedComponents: [],
|
|
2952
|
+
failedOptionalComponents,
|
|
2953
|
+
skippedDueToDependency: Array.from(skippedDueToDependency),
|
|
2954
|
+
reason: unexpectedStopResult.requiredFailure.error.message,
|
|
2955
|
+
code: "component_unexpected_stop",
|
|
2956
|
+
error: unexpectedStopResult.requiredFailure.error,
|
|
2957
|
+
durationMS: Date.now() - startTime
|
|
2958
|
+
};
|
|
2959
|
+
}
|
|
2841
2960
|
this.updateStartedFlag();
|
|
2842
2961
|
const skippedComponentsArray = [
|
|
2843
2962
|
...Array.from(skippedDueToDependency),
|
|
@@ -2869,10 +2988,12 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2869
2988
|
if (timeoutHandle) {
|
|
2870
2989
|
clearTimeout(timeoutHandle);
|
|
2871
2990
|
}
|
|
2872
|
-
|
|
2991
|
+
this.isStarting = false;
|
|
2992
|
+
if (didAutoAttachSignalsForBulkStartup || this.autoAttachedSignalsDuringStartup) {
|
|
2873
2993
|
this.autoDetachSignalsIfIdle("failed bulk startup");
|
|
2874
2994
|
}
|
|
2875
|
-
this.
|
|
2995
|
+
this.autoAttachedSignalsDuringStartup = false;
|
|
2996
|
+
this.unexpectedStopsDuringStartup.clear();
|
|
2876
2997
|
}
|
|
2877
2998
|
}
|
|
2878
2999
|
/**
|
|
@@ -2936,7 +3057,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2936
3057
|
return {
|
|
2937
3058
|
success: false,
|
|
2938
3059
|
componentName: name,
|
|
2939
|
-
reason:
|
|
3060
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_BULK_STARTUP_IN_PROGRESS,
|
|
2940
3061
|
code: "startup_in_progress"
|
|
2941
3062
|
};
|
|
2942
3063
|
}
|
|
@@ -2947,7 +3068,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2947
3068
|
return {
|
|
2948
3069
|
success: false,
|
|
2949
3070
|
componentName: name,
|
|
2950
|
-
reason:
|
|
3071
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_SHUTDOWN_IN_PROGRESS,
|
|
2951
3072
|
code: "shutdown_in_progress"
|
|
2952
3073
|
};
|
|
2953
3074
|
}
|
|
@@ -2981,7 +3102,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2981
3102
|
return {
|
|
2982
3103
|
success: false,
|
|
2983
3104
|
componentName: name,
|
|
2984
|
-
reason: this.isStarting ?
|
|
3105
|
+
reason: this.isStarting ? LIFECYCLE_MANAGER_MESSAGE_BULK_STARTUP_IN_PROGRESS : LIFECYCLE_MANAGER_MESSAGE_SHUTDOWN_IN_PROGRESS,
|
|
2985
3106
|
code: this.isStarting ? "startup_in_progress" : "shutdown_in_progress"
|
|
2986
3107
|
};
|
|
2987
3108
|
}
|
|
@@ -3163,7 +3284,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3163
3284
|
if (this.isShuttingDown) {
|
|
3164
3285
|
if (isFirstExit && this.pendingLoggerExitResolve === null) {
|
|
3165
3286
|
this.logger.debug(
|
|
3166
|
-
|
|
3287
|
+
LIFECYCLE_MANAGER_LOG_LOGGER_EXIT_DURING_SHUTDOWN,
|
|
3167
3288
|
{
|
|
3168
3289
|
params: { exitCode }
|
|
3169
3290
|
}
|
|
@@ -3172,7 +3293,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3172
3293
|
this.pendingLoggerExitResolve = resolve;
|
|
3173
3294
|
});
|
|
3174
3295
|
}
|
|
3175
|
-
this.logger.debug(
|
|
3296
|
+
this.logger.debug(LIFECYCLE_MANAGER_LOG_LOGGER_EXIT_DURING_SHUTDOWN, {
|
|
3176
3297
|
params: { exitCode }
|
|
3177
3298
|
});
|
|
3178
3299
|
return { action: "wait" };
|
|
@@ -3262,7 +3383,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3262
3383
|
return {
|
|
3263
3384
|
name,
|
|
3264
3385
|
healthy: false,
|
|
3265
|
-
message:
|
|
3386
|
+
message: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_FOUND,
|
|
3266
3387
|
checkedAt: startTime,
|
|
3267
3388
|
durationMS: 0,
|
|
3268
3389
|
error: null,
|
|
@@ -3275,7 +3396,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3275
3396
|
return {
|
|
3276
3397
|
name,
|
|
3277
3398
|
healthy: false,
|
|
3278
|
-
message: isStalled ?
|
|
3399
|
+
message: isStalled ? LIFECYCLE_MANAGER_MESSAGE_COMPONENT_STALLED : LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_RUNNING,
|
|
3279
3400
|
checkedAt: startTime,
|
|
3280
3401
|
durationMS: Date.now() - startTime,
|
|
3281
3402
|
error: null,
|
|
@@ -3491,7 +3612,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3491
3612
|
result = component.onMessage(payload, from);
|
|
3492
3613
|
} catch (error) {
|
|
3493
3614
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
3494
|
-
this.logger.entity(componentName).error(
|
|
3615
|
+
this.logger.entity(componentName).error(LIFECYCLE_MANAGER_LOG_MESSAGE_HANDLER_FAILED, {
|
|
3495
3616
|
params: { error: err, from }
|
|
3496
3617
|
});
|
|
3497
3618
|
this.lifecycleEvents.componentMessageFailed(componentName, from, err, {
|
|
@@ -3551,7 +3672,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3551
3672
|
};
|
|
3552
3673
|
} catch (error) {
|
|
3553
3674
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
3554
|
-
this.logger.entity(componentName).error(
|
|
3675
|
+
this.logger.entity(componentName).error(LIFECYCLE_MANAGER_LOG_MESSAGE_HANDLER_FAILED, {
|
|
3555
3676
|
params: { error: err, from, timeoutMS }
|
|
3556
3677
|
});
|
|
3557
3678
|
this.lifecycleEvents.componentMessageFailed(componentName, from, err, {
|
|
@@ -3824,7 +3945,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3824
3945
|
this.lifecycleEvents.componentRegistrationRejected({
|
|
3825
3946
|
name: componentName,
|
|
3826
3947
|
reason: "shutdown_in_progress",
|
|
3827
|
-
message:
|
|
3948
|
+
message: LIFECYCLE_MANAGER_MESSAGE_REGISTER_SHUTDOWN_IN_PROGRESS,
|
|
3828
3949
|
registrationIndexBefore,
|
|
3829
3950
|
registrationIndexAfter: registrationIndexBefore,
|
|
3830
3951
|
requestedPosition: isInsertAction ? { position, targetComponentName } : void 0,
|
|
@@ -3836,7 +3957,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3836
3957
|
targetComponentName,
|
|
3837
3958
|
registrationIndexBefore,
|
|
3838
3959
|
code: "shutdown_in_progress",
|
|
3839
|
-
reason:
|
|
3960
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_REGISTER_SHUTDOWN_IN_PROGRESS,
|
|
3840
3961
|
targetFound: void 0
|
|
3841
3962
|
});
|
|
3842
3963
|
}
|
|
@@ -3847,7 +3968,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3847
3968
|
this.lifecycleEvents.componentRegistrationRejected({
|
|
3848
3969
|
name: componentName,
|
|
3849
3970
|
reason: "startup_in_progress",
|
|
3850
|
-
message:
|
|
3971
|
+
message: LIFECYCLE_MANAGER_MESSAGE_REGISTER_REQUIRED_DEPENDENCY_DURING_STARTUP,
|
|
3851
3972
|
registrationIndexBefore,
|
|
3852
3973
|
registrationIndexAfter: registrationIndexBefore,
|
|
3853
3974
|
requestedPosition: isInsertAction ? { position, targetComponentName } : void 0,
|
|
@@ -3859,7 +3980,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3859
3980
|
targetComponentName,
|
|
3860
3981
|
registrationIndexBefore,
|
|
3861
3982
|
code: "startup_in_progress",
|
|
3862
|
-
reason:
|
|
3983
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_REGISTER_REQUIRED_DEPENDENCY_DURING_STARTUP,
|
|
3863
3984
|
targetFound: void 0
|
|
3864
3985
|
});
|
|
3865
3986
|
}
|
|
@@ -3868,7 +3989,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3868
3989
|
this.lifecycleEvents.componentRegistrationRejected({
|
|
3869
3990
|
name: componentName,
|
|
3870
3991
|
reason: "duplicate_instance",
|
|
3871
|
-
message:
|
|
3992
|
+
message: LIFECYCLE_MANAGER_MESSAGE_DUPLICATE_COMPONENT_INSTANCE,
|
|
3872
3993
|
registrationIndexBefore,
|
|
3873
3994
|
registrationIndexAfter: registrationIndexBefore,
|
|
3874
3995
|
requestedPosition: isInsertAction ? { position, targetComponentName } : void 0,
|
|
@@ -3880,7 +4001,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3880
4001
|
targetComponentName,
|
|
3881
4002
|
registrationIndexBefore,
|
|
3882
4003
|
code: "duplicate_instance",
|
|
3883
|
-
reason:
|
|
4004
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_DUPLICATE_COMPONENT_INSTANCE,
|
|
3884
4005
|
targetFound: void 0
|
|
3885
4006
|
});
|
|
3886
4007
|
}
|
|
@@ -4194,8 +4315,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4194
4315
|
const runningComponentsToStop = shutdownOrder.filter(
|
|
4195
4316
|
(name) => this.isComponentRunning(name) || shouldRetryStalled && stalledComponentNames.has(name)
|
|
4196
4317
|
);
|
|
4197
|
-
const stoppedComponents =
|
|
4198
|
-
const stalledComponents = [];
|
|
4318
|
+
const stoppedComponents = /* @__PURE__ */ new Set();
|
|
4199
4319
|
let hasTimedOut = false;
|
|
4200
4320
|
let timeoutHandle;
|
|
4201
4321
|
try {
|
|
@@ -4226,34 +4346,37 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4226
4346
|
this.logger.entity(name).info("Stopping component");
|
|
4227
4347
|
const isRunning = this.isComponentRunning(name);
|
|
4228
4348
|
const isStalled = stalledComponentNames.has(name);
|
|
4349
|
+
const currentState = this.componentStates.get(name);
|
|
4350
|
+
if (currentState === "stopped") {
|
|
4351
|
+
stoppedComponents.add(name);
|
|
4352
|
+
continue;
|
|
4353
|
+
}
|
|
4229
4354
|
const result2 = isRunning ? await this.stopComponentInternal(name) : shouldRetryStalled && isStalled ? await this.retryStalledComponent(name) : isStalled ? {
|
|
4230
4355
|
success: false,
|
|
4231
4356
|
componentName: name,
|
|
4232
|
-
reason:
|
|
4357
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_STALLED,
|
|
4233
4358
|
code: "component_stalled",
|
|
4234
4359
|
status: this.getComponentStatus(name)
|
|
4235
4360
|
} : {
|
|
4236
4361
|
success: false,
|
|
4237
4362
|
componentName: name,
|
|
4238
|
-
reason:
|
|
4363
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_RUNNING,
|
|
4239
4364
|
code: "component_not_running",
|
|
4240
4365
|
status: this.getComponentStatus(name)
|
|
4241
4366
|
};
|
|
4242
4367
|
if (result2.success) {
|
|
4243
|
-
stoppedComponents.
|
|
4368
|
+
stoppedComponents.add(name);
|
|
4244
4369
|
} else {
|
|
4245
4370
|
this.logger.entity(name).error(
|
|
4246
4371
|
"Component failed to stop, continuing with others: {{error.message}}",
|
|
4247
4372
|
{
|
|
4248
4373
|
params: {
|
|
4249
|
-
error: result2.error || new Error(
|
|
4374
|
+
error: result2.error || new Error(
|
|
4375
|
+
result2.reason || LIFECYCLE_MANAGER_MESSAGE_UNKNOWN_ERROR
|
|
4376
|
+
)
|
|
4250
4377
|
}
|
|
4251
4378
|
}
|
|
4252
4379
|
);
|
|
4253
|
-
const stallInfo = this.stalledComponents.get(name);
|
|
4254
|
-
if (stallInfo) {
|
|
4255
|
-
stalledComponents.push(stallInfo);
|
|
4256
|
-
}
|
|
4257
4380
|
if (shouldHaltOnStall) {
|
|
4258
4381
|
this.logger.warn(
|
|
4259
4382
|
"Halting shutdown after stall (haltOnStall=true)",
|
|
@@ -4269,21 +4392,32 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4269
4392
|
} else {
|
|
4270
4393
|
await shutdownOperation();
|
|
4271
4394
|
}
|
|
4395
|
+
const finalStalledNames = /* @__PURE__ */ new Set();
|
|
4396
|
+
for (const name of runningComponentsToStop) {
|
|
4397
|
+
if (this.stalledComponents.has(name)) {
|
|
4398
|
+
finalStalledNames.add(name);
|
|
4399
|
+
}
|
|
4400
|
+
}
|
|
4272
4401
|
if (!shouldRetryStalled) {
|
|
4273
4402
|
for (const name of stalledComponentNames) {
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
stalledComponents.push(stallInfo);
|
|
4403
|
+
if (this.stalledComponents.has(name)) {
|
|
4404
|
+
finalStalledNames.add(name);
|
|
4277
4405
|
}
|
|
4278
4406
|
}
|
|
4279
4407
|
}
|
|
4408
|
+
for (const name of runningComponentsToStop) {
|
|
4409
|
+
if (!finalStalledNames.has(name) && this.componentStates.get(name) === "stopped") {
|
|
4410
|
+
stoppedComponents.add(name);
|
|
4411
|
+
}
|
|
4412
|
+
}
|
|
4413
|
+
const stalledComponents = Array.from(finalStalledNames).map((name) => this.stalledComponents.get(name)).filter((stallInfo) => !!stallInfo);
|
|
4280
4414
|
const durationMS = Date.now() - startTime;
|
|
4281
4415
|
const isSuccess = !hasTimedOut && stalledComponents.length === 0;
|
|
4282
4416
|
this.logger[isSuccess ? "success" : "warn"](
|
|
4283
4417
|
isSuccess ? "Shutdown completed successfully" : "Shutdown attempt completed with stalled components or timeout",
|
|
4284
4418
|
{
|
|
4285
4419
|
params: {
|
|
4286
|
-
stopped: stoppedComponents.
|
|
4420
|
+
stopped: stoppedComponents.size,
|
|
4287
4421
|
stalled: stalledComponents.length,
|
|
4288
4422
|
durationMS
|
|
4289
4423
|
}
|
|
@@ -4291,7 +4425,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4291
4425
|
);
|
|
4292
4426
|
const result = {
|
|
4293
4427
|
success: isSuccess,
|
|
4294
|
-
stoppedComponents,
|
|
4428
|
+
stoppedComponents: Array.from(stoppedComponents),
|
|
4295
4429
|
stalledComponents,
|
|
4296
4430
|
durationMS,
|
|
4297
4431
|
timedOut: hasTimedOut || void 0,
|
|
@@ -4342,7 +4476,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4342
4476
|
return {
|
|
4343
4477
|
success: false,
|
|
4344
4478
|
componentName: name,
|
|
4345
|
-
reason:
|
|
4479
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_FOUND,
|
|
4346
4480
|
code: "component_not_found"
|
|
4347
4481
|
};
|
|
4348
4482
|
}
|
|
@@ -4353,12 +4487,15 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4353
4487
|
return {
|
|
4354
4488
|
success: false,
|
|
4355
4489
|
componentName: name,
|
|
4356
|
-
reason:
|
|
4490
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_RUNNING,
|
|
4357
4491
|
code: "component_not_running",
|
|
4358
4492
|
status: this.getComponentStatus(name)
|
|
4359
4493
|
};
|
|
4360
4494
|
}
|
|
4361
4495
|
this.logger.entity(name).warn("Retrying stalled component shutdown (force phase)");
|
|
4496
|
+
if (component.onShutdownForce) {
|
|
4497
|
+
this.issueStopAttemptToken(name);
|
|
4498
|
+
}
|
|
4362
4499
|
return this.shutdownComponentForce(name, component, {
|
|
4363
4500
|
gracefulPhaseRan: false,
|
|
4364
4501
|
gracefulTimedOut: false,
|
|
@@ -4378,7 +4515,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4378
4515
|
return {
|
|
4379
4516
|
success: false,
|
|
4380
4517
|
componentName: name,
|
|
4381
|
-
reason:
|
|
4518
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_SHUTDOWN_IN_PROGRESS,
|
|
4382
4519
|
code: "shutdown_in_progress"
|
|
4383
4520
|
};
|
|
4384
4521
|
}
|
|
@@ -4390,7 +4527,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4390
4527
|
return {
|
|
4391
4528
|
success: false,
|
|
4392
4529
|
componentName: name,
|
|
4393
|
-
reason:
|
|
4530
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_BULK_STARTUP_IN_PROGRESS,
|
|
4394
4531
|
code: "startup_in_progress"
|
|
4395
4532
|
};
|
|
4396
4533
|
}
|
|
@@ -4400,7 +4537,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4400
4537
|
return {
|
|
4401
4538
|
success: false,
|
|
4402
4539
|
componentName: name,
|
|
4403
|
-
reason:
|
|
4540
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_FOUND,
|
|
4404
4541
|
code: "component_not_found"
|
|
4405
4542
|
};
|
|
4406
4543
|
}
|
|
@@ -4409,7 +4546,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4409
4546
|
return {
|
|
4410
4547
|
success: false,
|
|
4411
4548
|
componentName: name,
|
|
4412
|
-
reason:
|
|
4549
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_STALLED,
|
|
4413
4550
|
code: "component_stalled",
|
|
4414
4551
|
status: this.getComponentStatus(name)
|
|
4415
4552
|
};
|
|
@@ -4473,6 +4610,9 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4473
4610
|
const timeoutMS = component.startupTimeoutMS;
|
|
4474
4611
|
const startAttemptToken = (0, import_ulid2.ulid)();
|
|
4475
4612
|
this.componentStartAttemptTokens.set(name, startAttemptToken);
|
|
4613
|
+
component._setUnexpectedStopHandler(
|
|
4614
|
+
(error) => this.handleComponentUnexpectedStop(name, startAttemptToken, error)
|
|
4615
|
+
);
|
|
4476
4616
|
const shutdownTokenAtStart = this.shutdownToken;
|
|
4477
4617
|
const didAutoAttachSignalsForComponentStartup = this.attachSignalsBeforeStartup ? this.autoAttachSignals("component startup") : false;
|
|
4478
4618
|
let timeoutHandle;
|
|
@@ -4515,6 +4655,18 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4515
4655
|
} else {
|
|
4516
4656
|
await startPromise;
|
|
4517
4657
|
}
|
|
4658
|
+
if (this.componentStartAttemptTokens.get(name) === startAttemptToken && this.componentStates.get(name) === "stopped" && !this.runningComponents.has(name)) {
|
|
4659
|
+
component._clearUnexpectedStopHandler();
|
|
4660
|
+
const error = this.componentErrors.get(name) ?? new Error(`Component "${name}" stopped unexpectedly during startup`);
|
|
4661
|
+
return {
|
|
4662
|
+
success: false,
|
|
4663
|
+
componentName: name,
|
|
4664
|
+
reason: error.message,
|
|
4665
|
+
code: "component_unexpected_stop",
|
|
4666
|
+
error,
|
|
4667
|
+
status: this.getComponentStatus(name)
|
|
4668
|
+
};
|
|
4669
|
+
}
|
|
4518
4670
|
if (this.isShuttingDown || shutdownTokenAtStart !== this.shutdownToken) {
|
|
4519
4671
|
this.componentStates.set(name, "running");
|
|
4520
4672
|
this.runningComponents.add(name);
|
|
@@ -4542,6 +4694,9 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4542
4694
|
this.componentStates.set(name, "running");
|
|
4543
4695
|
this.runningComponents.add(name);
|
|
4544
4696
|
this.stalledComponents.delete(name);
|
|
4697
|
+
if (shouldForceStalled) {
|
|
4698
|
+
this.issueStopAttemptToken(name);
|
|
4699
|
+
}
|
|
4545
4700
|
this.updateStartedFlag();
|
|
4546
4701
|
if (this.attachSignalsOnStart && this.runningComponents.size === 1) {
|
|
4547
4702
|
this.autoAttachSignals("first component start");
|
|
@@ -4561,9 +4716,24 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4561
4716
|
status: this.getComponentStatus(name)
|
|
4562
4717
|
};
|
|
4563
4718
|
} catch (error) {
|
|
4719
|
+
component._clearUnexpectedStopHandler();
|
|
4564
4720
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
4721
|
+
const isStartupTimeout = err instanceof ComponentStartTimeoutError && err.additionalInfo.componentName === name;
|
|
4722
|
+
const unexpectedStopError = this.componentErrors.get(name);
|
|
4723
|
+
if (this.componentStartAttemptTokens.get(name) === startAttemptToken && this.componentStates.get(name) === "stopped" && !this.runningComponents.has(name) && (isStartupTimeout || unexpectedStopError instanceof Error)) {
|
|
4724
|
+
return {
|
|
4725
|
+
success: false,
|
|
4726
|
+
componentName: name,
|
|
4727
|
+
reason: unexpectedStopError?.message || `Component "${name}" stopped unexpectedly during startup`,
|
|
4728
|
+
code: "component_unexpected_stop",
|
|
4729
|
+
error: unexpectedStopError || new Error(
|
|
4730
|
+
`Component "${name}" stopped unexpectedly during startup`
|
|
4731
|
+
),
|
|
4732
|
+
status: this.getComponentStatus(name)
|
|
4733
|
+
};
|
|
4734
|
+
}
|
|
4565
4735
|
this.componentErrors.set(name, err);
|
|
4566
|
-
if (
|
|
4736
|
+
if (isStartupTimeout) {
|
|
4567
4737
|
this.componentStates.set(name, "starting-timed-out");
|
|
4568
4738
|
this.logger.entity(name).error("Component startup timed out: {{error.message}}", {
|
|
4569
4739
|
params: { error: err }
|
|
@@ -4608,7 +4778,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4608
4778
|
return {
|
|
4609
4779
|
success: false,
|
|
4610
4780
|
componentName: name,
|
|
4611
|
-
reason:
|
|
4781
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_FOUND,
|
|
4612
4782
|
code: "component_not_found"
|
|
4613
4783
|
};
|
|
4614
4784
|
}
|
|
@@ -4616,7 +4786,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4616
4786
|
return {
|
|
4617
4787
|
success: false,
|
|
4618
4788
|
componentName: name,
|
|
4619
|
-
reason:
|
|
4789
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_STALLED,
|
|
4620
4790
|
code: "component_stalled",
|
|
4621
4791
|
status: this.getComponentStatus(name)
|
|
4622
4792
|
};
|
|
@@ -4625,7 +4795,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4625
4795
|
return {
|
|
4626
4796
|
success: false,
|
|
4627
4797
|
componentName: name,
|
|
4628
|
-
reason:
|
|
4798
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_RUNNING,
|
|
4629
4799
|
code: "component_not_running",
|
|
4630
4800
|
status: this.getComponentStatus(name)
|
|
4631
4801
|
};
|
|
@@ -4641,6 +4811,10 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4641
4811
|
};
|
|
4642
4812
|
}
|
|
4643
4813
|
if (options?.forceImmediate) {
|
|
4814
|
+
if (component.onShutdownForce) {
|
|
4815
|
+
this.issueStopAttemptToken(name);
|
|
4816
|
+
}
|
|
4817
|
+
component._clearUnexpectedStopHandler();
|
|
4644
4818
|
return this.shutdownComponentForce(name, component, {
|
|
4645
4819
|
gracefulPhaseRan: false,
|
|
4646
4820
|
gracefulTimedOut: false,
|
|
@@ -4777,9 +4951,11 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4777
4951
|
* Calls stop() with timeout
|
|
4778
4952
|
*/
|
|
4779
4953
|
async shutdownComponentGraceful(name, component, options) {
|
|
4954
|
+
component._clearUnexpectedStopHandler();
|
|
4780
4955
|
this.componentStates.set(name, "stopping");
|
|
4781
4956
|
this.logger.entity(name).info("Graceful shutdown started");
|
|
4782
4957
|
this.lifecycleEvents.componentStopping(name);
|
|
4958
|
+
const stopAttemptToken = this.issueStopAttemptToken(name);
|
|
4783
4959
|
const timeoutMS = options?.timeout ?? component.shutdownGracefulTimeoutMS;
|
|
4784
4960
|
let timeoutHandle;
|
|
4785
4961
|
try {
|
|
@@ -4800,7 +4976,16 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4800
4976
|
);
|
|
4801
4977
|
}
|
|
4802
4978
|
}
|
|
4803
|
-
Promise.resolve(stopPromise).
|
|
4979
|
+
Promise.resolve(stopPromise).then(
|
|
4980
|
+
() => this.handleLateStopResolution(
|
|
4981
|
+
name,
|
|
4982
|
+
stopAttemptToken,
|
|
4983
|
+
"graceful"
|
|
4984
|
+
),
|
|
4985
|
+
() => {
|
|
4986
|
+
}
|
|
4987
|
+
// Intentionally ignore errors after timeout
|
|
4988
|
+
).catch(() => {
|
|
4804
4989
|
});
|
|
4805
4990
|
reject(
|
|
4806
4991
|
new ComponentStopTimeoutError({
|
|
@@ -4819,9 +5004,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4819
5004
|
this.stalledComponents.delete(name);
|
|
4820
5005
|
this.updateStartedFlag();
|
|
4821
5006
|
if (this.detachSignalsOnStop && this.runningComponents.size === 0 && this.processSignalManager) {
|
|
4822
|
-
this.logger.info(
|
|
4823
|
-
"Auto-detaching process signals on last component stop"
|
|
4824
|
-
);
|
|
5007
|
+
this.logger.info(LIFECYCLE_MANAGER_LOG_AUTO_DETACH_LAST_COMPONENT_STOP);
|
|
4825
5008
|
this.detachSignals();
|
|
4826
5009
|
}
|
|
4827
5010
|
const timestamps = this.componentTimestamps.get(name) ?? {
|
|
@@ -4844,15 +5027,15 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4844
5027
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
4845
5028
|
this.componentErrors.set(name, err);
|
|
4846
5029
|
if (err instanceof ComponentStopTimeoutError && err.additionalInfo.componentName === name) {
|
|
4847
|
-
this.logger.entity(name).warn(
|
|
5030
|
+
this.logger.entity(name).warn(LIFECYCLE_MANAGER_MESSAGE_GRACEFUL_SHUTDOWN_TIMED_OUT);
|
|
4848
5031
|
this.lifecycleEvents.componentStopTimeout(name, err, {
|
|
4849
5032
|
timeoutMS,
|
|
4850
|
-
reason:
|
|
5033
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_GRACEFUL_SHUTDOWN_TIMED_OUT
|
|
4851
5034
|
});
|
|
4852
5035
|
return {
|
|
4853
5036
|
success: false,
|
|
4854
5037
|
componentName: name,
|
|
4855
|
-
reason:
|
|
5038
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_GRACEFUL_SHUTDOWN_TIMED_OUT,
|
|
4856
5039
|
code: "component_shutdown_timeout",
|
|
4857
5040
|
error: err,
|
|
4858
5041
|
status: this.getComponentStatus(name)
|
|
@@ -4895,8 +5078,6 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4895
5078
|
gracefulTimedOut: context.gracefulTimedOut
|
|
4896
5079
|
}
|
|
4897
5080
|
});
|
|
4898
|
-
const timeoutMS = component.shutdownForceTimeoutMS;
|
|
4899
|
-
let timeoutHandle;
|
|
4900
5081
|
if (!component.onShutdownForce) {
|
|
4901
5082
|
const stallInfo = {
|
|
4902
5083
|
name,
|
|
@@ -4930,6 +5111,9 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4930
5111
|
status: this.getComponentStatus(name)
|
|
4931
5112
|
};
|
|
4932
5113
|
}
|
|
5114
|
+
const timeoutMS = component.shutdownForceTimeoutMS;
|
|
5115
|
+
const { promise: stoppedDuringForcePromise, cleanup: cleanupForceWaiter } = this.createPendingForceStopWaiter(name);
|
|
5116
|
+
let timeoutHandle;
|
|
4933
5117
|
try {
|
|
4934
5118
|
const forcePromise = component.onShutdownForce();
|
|
4935
5119
|
if (timeoutMS > 0) {
|
|
@@ -4948,23 +5132,44 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4948
5132
|
);
|
|
4949
5133
|
}
|
|
4950
5134
|
}
|
|
4951
|
-
|
|
5135
|
+
const forceAttemptToken = this.componentStopAttemptTokens.get(name) ?? (0, import_ulid2.ulid)();
|
|
5136
|
+
Promise.resolve(forcePromise).then(
|
|
5137
|
+
() => this.handleLateStopResolution(
|
|
5138
|
+
name,
|
|
5139
|
+
forceAttemptToken,
|
|
5140
|
+
"force"
|
|
5141
|
+
),
|
|
5142
|
+
() => {
|
|
5143
|
+
}
|
|
5144
|
+
// Intentionally ignore errors after timeout
|
|
5145
|
+
).catch(() => {
|
|
4952
5146
|
});
|
|
4953
|
-
reject(
|
|
5147
|
+
reject(
|
|
5148
|
+
new Error(LIFECYCLE_MANAGER_MESSAGE_FORCE_SHUTDOWN_TIMED_OUT)
|
|
5149
|
+
);
|
|
4954
5150
|
}, timeoutMS);
|
|
4955
5151
|
});
|
|
4956
|
-
await Promise.race([
|
|
5152
|
+
await Promise.race([
|
|
5153
|
+
forcePromise,
|
|
5154
|
+
timeoutPromise,
|
|
5155
|
+
stoppedDuringForcePromise
|
|
5156
|
+
]);
|
|
4957
5157
|
} else {
|
|
4958
|
-
await forcePromise;
|
|
5158
|
+
await Promise.race([forcePromise, stoppedDuringForcePromise]);
|
|
5159
|
+
}
|
|
5160
|
+
if (this.componentStates.get(name) === "stopped" && !this.runningComponents.has(name)) {
|
|
5161
|
+
return {
|
|
5162
|
+
success: true,
|
|
5163
|
+
componentName: name,
|
|
5164
|
+
status: this.getComponentStatus(name)
|
|
5165
|
+
};
|
|
4959
5166
|
}
|
|
4960
5167
|
this.componentStates.set(name, "stopped");
|
|
4961
5168
|
this.runningComponents.delete(name);
|
|
4962
5169
|
this.stalledComponents.delete(name);
|
|
4963
5170
|
this.updateStartedFlag();
|
|
4964
5171
|
if (this.detachSignalsOnStop && this.runningComponents.size === 0 && this.processSignalManager) {
|
|
4965
|
-
this.logger.info(
|
|
4966
|
-
"Auto-detaching process signals on last component stop"
|
|
4967
|
-
);
|
|
5172
|
+
this.logger.info(LIFECYCLE_MANAGER_LOG_AUTO_DETACH_LAST_COMPONENT_STOP);
|
|
4968
5173
|
this.detachSignals();
|
|
4969
5174
|
}
|
|
4970
5175
|
const timestamps = this.componentTimestamps.get(name) ?? {
|
|
@@ -4985,8 +5190,15 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4985
5190
|
status: this.getComponentStatus(name)
|
|
4986
5191
|
};
|
|
4987
5192
|
} catch (error) {
|
|
5193
|
+
if (this.componentStates.get(name) === "stopped" && !this.runningComponents.has(name)) {
|
|
5194
|
+
return {
|
|
5195
|
+
success: true,
|
|
5196
|
+
componentName: name,
|
|
5197
|
+
status: this.getComponentStatus(name)
|
|
5198
|
+
};
|
|
5199
|
+
}
|
|
4988
5200
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
4989
|
-
const isTimeout = err.message ===
|
|
5201
|
+
const isTimeout = err.message === LIFECYCLE_MANAGER_MESSAGE_FORCE_SHUTDOWN_TIMED_OUT;
|
|
4990
5202
|
const stallInfo = {
|
|
4991
5203
|
name,
|
|
4992
5204
|
phase: "force",
|
|
@@ -5017,12 +5229,13 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
5017
5229
|
return {
|
|
5018
5230
|
success: false,
|
|
5019
5231
|
componentName: name,
|
|
5020
|
-
reason: isTimeout ?
|
|
5232
|
+
reason: isTimeout ? LIFECYCLE_MANAGER_MESSAGE_FORCE_SHUTDOWN_TIMED_OUT : err.message,
|
|
5021
5233
|
code: isTimeout ? "component_shutdown_timeout" : "unknown_error",
|
|
5022
5234
|
error: err,
|
|
5023
5235
|
status: this.getComponentStatus(name)
|
|
5024
5236
|
};
|
|
5025
5237
|
} finally {
|
|
5238
|
+
cleanupForceWaiter();
|
|
5026
5239
|
if (timeoutHandle) {
|
|
5027
5240
|
clearTimeout(timeoutHandle);
|
|
5028
5241
|
}
|
|
@@ -5099,7 +5312,9 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
5099
5312
|
"Failed to stop component during rollback, continuing: {{error.message}}",
|
|
5100
5313
|
{
|
|
5101
5314
|
params: {
|
|
5102
|
-
error: result.error || new Error(
|
|
5315
|
+
error: result.error || new Error(
|
|
5316
|
+
result.reason || LIFECYCLE_MANAGER_MESSAGE_UNKNOWN_ERROR
|
|
5317
|
+
)
|
|
5103
5318
|
}
|
|
5104
5319
|
}
|
|
5105
5320
|
);
|
|
@@ -5113,10 +5328,13 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
5113
5328
|
}
|
|
5114
5329
|
this.logger.info(`Auto-attaching process signals on ${trigger}`);
|
|
5115
5330
|
this.attachSignals();
|
|
5331
|
+
if (this.isStarting) {
|
|
5332
|
+
this.autoAttachedSignalsDuringStartup = true;
|
|
5333
|
+
}
|
|
5116
5334
|
return true;
|
|
5117
5335
|
}
|
|
5118
5336
|
autoDetachSignalsIfIdle(trigger) {
|
|
5119
|
-
if (!this.detachSignalsOnStop || this.runningComponents.size > 0 || !this.processSignalManager?.getStatus().isAttached) {
|
|
5337
|
+
if (!this.detachSignalsOnStop || this.isStarting || this.runningComponents.size > 0 || !this.processSignalManager?.getStatus().isAttached) {
|
|
5120
5338
|
return;
|
|
5121
5339
|
}
|
|
5122
5340
|
this.logger.info(`Auto-detaching process signals after ${trigger}`);
|
|
@@ -5158,6 +5376,210 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
5158
5376
|
}).catch(() => {
|
|
5159
5377
|
});
|
|
5160
5378
|
}
|
|
5379
|
+
consumeUnexpectedStopsDuringStartup(startedComponents, failedOptionalComponents) {
|
|
5380
|
+
if (this.unexpectedStopsDuringStartup.size === 0) {
|
|
5381
|
+
return { startedComponents: [...startedComponents] };
|
|
5382
|
+
}
|
|
5383
|
+
const remainingStartedComponents = [];
|
|
5384
|
+
let requiredFailure;
|
|
5385
|
+
for (const name of startedComponents) {
|
|
5386
|
+
const startupStopError = this.unexpectedStopsDuringStartup.get(name);
|
|
5387
|
+
if (startupStopError === void 0) {
|
|
5388
|
+
remainingStartedComponents.push(name);
|
|
5389
|
+
continue;
|
|
5390
|
+
}
|
|
5391
|
+
this.unexpectedStopsDuringStartup.delete(name);
|
|
5392
|
+
const error = startupStopError ?? new Error(`Component "${name}" stopped unexpectedly during startup`);
|
|
5393
|
+
const component = this.getComponent(name);
|
|
5394
|
+
if (component?.isOptional()) {
|
|
5395
|
+
if (!failedOptionalComponents.some((entry) => entry.name === name)) {
|
|
5396
|
+
failedOptionalComponents.push({ name, error });
|
|
5397
|
+
}
|
|
5398
|
+
this.logger.entity(name).warn(
|
|
5399
|
+
LIFECYCLE_MANAGER_LOG_OPTIONAL_COMPONENT_UNEXPECTED_STOP_DURING_STARTUP,
|
|
5400
|
+
{
|
|
5401
|
+
params: { error }
|
|
5402
|
+
}
|
|
5403
|
+
);
|
|
5404
|
+
continue;
|
|
5405
|
+
}
|
|
5406
|
+
this.logger.entity(name).error(
|
|
5407
|
+
LIFECYCLE_MANAGER_LOG_REQUIRED_COMPONENT_UNEXPECTED_STOP_DURING_STARTUP,
|
|
5408
|
+
{
|
|
5409
|
+
params: { error }
|
|
5410
|
+
}
|
|
5411
|
+
);
|
|
5412
|
+
requiredFailure ??= { name, error };
|
|
5413
|
+
}
|
|
5414
|
+
return {
|
|
5415
|
+
startedComponents: remainingStartedComponents,
|
|
5416
|
+
requiredFailure
|
|
5417
|
+
};
|
|
5418
|
+
}
|
|
5419
|
+
/**
|
|
5420
|
+
* Issues and returns a unique stop attempt token for a component.
|
|
5421
|
+
*
|
|
5422
|
+
* Each stop attempt (graceful or force-retry) gets a unique token.
|
|
5423
|
+
* The late-resolution handler captures this token in its closure so it can
|
|
5424
|
+
* skip any stall entries that were created by a *later* stop attempt — e.g. a
|
|
5425
|
+
* force-retry that also timed out after the original graceful promise floated
|
|
5426
|
+
* in the background.
|
|
5427
|
+
*/
|
|
5428
|
+
issueStopAttemptToken(name) {
|
|
5429
|
+
const next = (0, import_ulid2.ulid)();
|
|
5430
|
+
this.componentStopAttemptTokens.set(name, next);
|
|
5431
|
+
return next;
|
|
5432
|
+
}
|
|
5433
|
+
createPendingForceStopWaiter(name) {
|
|
5434
|
+
let isResolved = false;
|
|
5435
|
+
let waiters = this.pendingForceStopWaiters.get(name);
|
|
5436
|
+
if (!waiters) {
|
|
5437
|
+
waiters = /* @__PURE__ */ new Set();
|
|
5438
|
+
this.pendingForceStopWaiters.set(name, waiters);
|
|
5439
|
+
}
|
|
5440
|
+
let resolveWaiter;
|
|
5441
|
+
const promise = new Promise((resolve) => {
|
|
5442
|
+
resolveWaiter = () => {
|
|
5443
|
+
if (isResolved) {
|
|
5444
|
+
return;
|
|
5445
|
+
}
|
|
5446
|
+
isResolved = true;
|
|
5447
|
+
resolve();
|
|
5448
|
+
};
|
|
5449
|
+
});
|
|
5450
|
+
waiters.add(resolveWaiter);
|
|
5451
|
+
return {
|
|
5452
|
+
promise,
|
|
5453
|
+
cleanup: () => {
|
|
5454
|
+
const pending = this.pendingForceStopWaiters.get(name);
|
|
5455
|
+
if (!pending) {
|
|
5456
|
+
return;
|
|
5457
|
+
}
|
|
5458
|
+
pending.delete(resolveWaiter);
|
|
5459
|
+
if (pending.size === 0) {
|
|
5460
|
+
this.pendingForceStopWaiters.delete(name);
|
|
5461
|
+
}
|
|
5462
|
+
}
|
|
5463
|
+
};
|
|
5464
|
+
}
|
|
5465
|
+
resolvePendingForceStopWaiters(name) {
|
|
5466
|
+
const waiters = this.pendingForceStopWaiters.get(name);
|
|
5467
|
+
if (!waiters || waiters.size === 0) {
|
|
5468
|
+
return;
|
|
5469
|
+
}
|
|
5470
|
+
this.pendingForceStopWaiters.delete(name);
|
|
5471
|
+
for (const resolve of waiters) {
|
|
5472
|
+
resolve();
|
|
5473
|
+
}
|
|
5474
|
+
}
|
|
5475
|
+
/**
|
|
5476
|
+
* Called when a stop promise eventually resolves after its timeout path already fired.
|
|
5477
|
+
*
|
|
5478
|
+
* Usually this means a previously stalled component's original stop() or
|
|
5479
|
+
* onShutdownForce() promise finally resolved, so the manager can clear the
|
|
5480
|
+
* stall and transition the component to stopped without a manual retry.
|
|
5481
|
+
*
|
|
5482
|
+
* There is one extra overlap case for graceful stop(): stop() can resolve
|
|
5483
|
+
* after the graceful timeout but before onShutdownForce() itself times out.
|
|
5484
|
+
* In that window no stall entry exists yet, but the component still finished
|
|
5485
|
+
* stopping cleanly, so we finalize it here and let the later force-timeout
|
|
5486
|
+
* path observe the already-stopped state and no-op. This overlap fix is
|
|
5487
|
+
* scoped to the same stop token and will not cross a later retry attempt.
|
|
5488
|
+
*
|
|
5489
|
+
* Two guards prevent stale floating promises from incorrectly clearing state:
|
|
5490
|
+
*
|
|
5491
|
+
* 1. token guard — if a newer stop attempt (e.g. a retryStalled
|
|
5492
|
+
* force-retry) has started since this promise was launched, its token
|
|
5493
|
+
* won't match and we bail out immediately.
|
|
5494
|
+
*
|
|
5495
|
+
* 2. state/stall guard — if the component was unregistered, restarted, or
|
|
5496
|
+
* already cleared by another path, there will be neither a matching stall
|
|
5497
|
+
* entry nor the force-phase overlap state, so we bail out.
|
|
5498
|
+
*/
|
|
5499
|
+
handleLateStopResolution(name, token, source) {
|
|
5500
|
+
if (this.componentStopAttemptTokens.get(name) !== token) {
|
|
5501
|
+
return;
|
|
5502
|
+
}
|
|
5503
|
+
const currentState = this.componentStates.get(name);
|
|
5504
|
+
const stallInfo = this.stalledComponents.get(name);
|
|
5505
|
+
const isCompletedDuringForcePhase = source === "graceful" && !stallInfo && currentState === "force-stopping";
|
|
5506
|
+
if (stallInfo && currentState !== "stalled") {
|
|
5507
|
+
this.stalledComponents.delete(name);
|
|
5508
|
+
return;
|
|
5509
|
+
}
|
|
5510
|
+
if (!stallInfo && !isCompletedDuringForcePhase) {
|
|
5511
|
+
return;
|
|
5512
|
+
}
|
|
5513
|
+
const stalledDurationMS = stallInfo ? Date.now() - stallInfo.stalledAt : void 0;
|
|
5514
|
+
if (stallInfo) {
|
|
5515
|
+
this.stalledComponents.delete(name);
|
|
5516
|
+
}
|
|
5517
|
+
this.componentStates.set(name, "stopped");
|
|
5518
|
+
this.runningComponents.delete(name);
|
|
5519
|
+
this.componentErrors.set(name, null);
|
|
5520
|
+
this.updateStartedFlag();
|
|
5521
|
+
this.resolvePendingForceStopWaiters(name);
|
|
5522
|
+
if (this.detachSignalsOnStop && this.runningComponents.size === 0 && this.processSignalManager) {
|
|
5523
|
+
this.logger.info(LIFECYCLE_MANAGER_LOG_AUTO_DETACH_LAST_COMPONENT_STOP);
|
|
5524
|
+
this.detachSignals();
|
|
5525
|
+
}
|
|
5526
|
+
const timestamps = this.componentTimestamps.get(name) ?? {
|
|
5527
|
+
startedAt: null,
|
|
5528
|
+
stoppedAt: null
|
|
5529
|
+
};
|
|
5530
|
+
timestamps.stoppedAt = Date.now();
|
|
5531
|
+
this.componentTimestamps.set(name, timestamps);
|
|
5532
|
+
this.logger.entity(name).info(
|
|
5533
|
+
stallInfo ? "Stalled component completed stop late, stall cleared" : "Graceful stop completed after force phase started",
|
|
5534
|
+
stalledDurationMS ? { params: { stalledDurationMS } } : void 0
|
|
5535
|
+
);
|
|
5536
|
+
if (source === "force") {
|
|
5537
|
+
this.lifecycleEvents.componentShutdownForceCompleted(name);
|
|
5538
|
+
}
|
|
5539
|
+
if (stallInfo && stalledDurationMS !== void 0) {
|
|
5540
|
+
this.lifecycleEvents.componentStalledResolved(
|
|
5541
|
+
name,
|
|
5542
|
+
stallInfo,
|
|
5543
|
+
stalledDurationMS
|
|
5544
|
+
);
|
|
5545
|
+
}
|
|
5546
|
+
this.lifecycleEvents.componentStopped(name, this.getComponentStatus(name));
|
|
5547
|
+
}
|
|
5548
|
+
handleComponentUnexpectedStop(name, startAttemptToken, error) {
|
|
5549
|
+
const currentState = this.componentStates.get(name);
|
|
5550
|
+
if (
|
|
5551
|
+
// Startup-time self-stops are valid too: start() may still be awaiting
|
|
5552
|
+
// some async work while an internal listener has already observed that
|
|
5553
|
+
// the component died and reported it.
|
|
5554
|
+
currentState !== "starting" && currentState !== "running" || this.componentStartAttemptTokens.get(name) !== startAttemptToken
|
|
5555
|
+
) {
|
|
5556
|
+
return false;
|
|
5557
|
+
}
|
|
5558
|
+
this.runningComponents.delete(name);
|
|
5559
|
+
this.componentStates.set(name, "stopped");
|
|
5560
|
+
this.componentErrors.set(name, error ?? null);
|
|
5561
|
+
if (this.isStarting) {
|
|
5562
|
+
this.unexpectedStopsDuringStartup.set(name, error ?? null);
|
|
5563
|
+
}
|
|
5564
|
+
this.updateStartedFlag();
|
|
5565
|
+
if (this.detachSignalsOnStop && !this.isStarting && this.runningComponents.size === 0 && this.processSignalManager) {
|
|
5566
|
+
this.logger.info(LIFECYCLE_MANAGER_LOG_AUTO_DETACH_LAST_COMPONENT_STOP);
|
|
5567
|
+
this.detachSignals();
|
|
5568
|
+
}
|
|
5569
|
+
const timestamps = this.componentTimestamps.get(name) ?? {
|
|
5570
|
+
startedAt: null,
|
|
5571
|
+
stoppedAt: null
|
|
5572
|
+
};
|
|
5573
|
+
timestamps.stoppedAt = Date.now();
|
|
5574
|
+
this.componentTimestamps.set(name, timestamps);
|
|
5575
|
+
this.logger.entity(name).warn(
|
|
5576
|
+
error ? `Component stopped unexpectedly: ${error.message}` : "Component stopped unexpectedly",
|
|
5577
|
+
{ params: { error } }
|
|
5578
|
+
);
|
|
5579
|
+
this.lifecycleEvents.componentUnexpectedStop(name, error);
|
|
5580
|
+
this.lifecycleEvents.componentStopped(name, this.getComponentStatus(name));
|
|
5581
|
+
return true;
|
|
5582
|
+
}
|
|
5161
5583
|
/**
|
|
5162
5584
|
* Safe emit wrapper - prevents event handler errors from breaking lifecycle
|
|
5163
5585
|
*/
|
|
@@ -5729,105 +6151,87 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
5729
6151
|
}
|
|
5730
6152
|
}
|
|
5731
6153
|
/**
|
|
5732
|
-
*
|
|
6154
|
+
* Shared dispatch path for reload/info/debug requests. Logs the dispatch,
|
|
6155
|
+
* emits the signal event, then either invokes the user-supplied callback
|
|
6156
|
+
* (passing the broadcast function so the user controls when/whether to
|
|
6157
|
+
* broadcast) or broadcasts directly when no callback is configured.
|
|
5733
6158
|
*
|
|
5734
6159
|
* When called from signal handlers (source='signal'), the Promise is started
|
|
5735
|
-
* but not awaited
|
|
5736
|
-
* still notified and the work completes
|
|
5737
|
-
*
|
|
6160
|
+
* but not awaited — Node.js signal handlers cannot return values, so results
|
|
6161
|
+
* are not accessible. Components are still notified and the work completes.
|
|
5738
6162
|
* When called from manual triggers (source='trigger'), the Promise is awaited
|
|
5739
6163
|
* and results are returned for programmatic use.
|
|
5740
|
-
*
|
|
5741
|
-
* @param source - Whether triggered from signal manager or manual trigger
|
|
5742
6164
|
*/
|
|
5743
|
-
async
|
|
5744
|
-
this.logger.info(
|
|
5745
|
-
|
|
5746
|
-
if (
|
|
5747
|
-
const
|
|
5748
|
-
const result = this.onReloadRequested(broadcastFn);
|
|
6165
|
+
async handleSignalRequest(descriptor, source) {
|
|
6166
|
+
this.logger.info(descriptor.dispatchedLogLabel, { params: { source } });
|
|
6167
|
+
descriptor.emitSignal();
|
|
6168
|
+
if (descriptor.customCallback) {
|
|
6169
|
+
const result = descriptor.customCallback(descriptor.broadcast);
|
|
5749
6170
|
if (isPromise(result)) {
|
|
5750
6171
|
await result;
|
|
5751
6172
|
}
|
|
5752
6173
|
return {
|
|
5753
|
-
signal:
|
|
6174
|
+
signal: descriptor.signal,
|
|
5754
6175
|
results: [],
|
|
5755
6176
|
timedOut: false,
|
|
5756
6177
|
code: "ok"
|
|
5757
6178
|
};
|
|
5758
6179
|
}
|
|
5759
|
-
return
|
|
6180
|
+
return descriptor.broadcast();
|
|
6181
|
+
}
|
|
6182
|
+
async handleReloadRequest(source = "trigger") {
|
|
6183
|
+
return this.handleSignalRequest(
|
|
6184
|
+
{
|
|
6185
|
+
signal: "reload",
|
|
6186
|
+
dispatchedLogLabel: "Reload dispatched",
|
|
6187
|
+
emitSignal: () => this.lifecycleEvents.signalReload(),
|
|
6188
|
+
customCallback: this.onReloadRequested,
|
|
6189
|
+
broadcast: () => this.broadcastReload()
|
|
6190
|
+
},
|
|
6191
|
+
source
|
|
6192
|
+
);
|
|
5760
6193
|
}
|
|
5761
|
-
/**
|
|
5762
|
-
* Handle info request - calls custom callback or broadcasts to components.
|
|
5763
|
-
*
|
|
5764
|
-
* When called from signal handlers, the Promise executes but return values
|
|
5765
|
-
* are not accessible due to Node.js signal handler constraints.
|
|
5766
|
-
*
|
|
5767
|
-
* @param source - Whether triggered from signal manager or manual trigger
|
|
5768
|
-
*/
|
|
5769
6194
|
async handleInfoRequest(source = "trigger") {
|
|
5770
|
-
this.
|
|
5771
|
-
|
|
5772
|
-
if (this.onInfoRequested) {
|
|
5773
|
-
const broadcastFn = () => this.broadcastInfo();
|
|
5774
|
-
const result = this.onInfoRequested(broadcastFn);
|
|
5775
|
-
if (isPromise(result)) {
|
|
5776
|
-
await result;
|
|
5777
|
-
}
|
|
5778
|
-
return {
|
|
6195
|
+
return this.handleSignalRequest(
|
|
6196
|
+
{
|
|
5779
6197
|
signal: "info",
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
6198
|
+
dispatchedLogLabel: "Info dispatched",
|
|
6199
|
+
emitSignal: () => this.lifecycleEvents.signalInfo(),
|
|
6200
|
+
customCallback: this.onInfoRequested,
|
|
6201
|
+
broadcast: () => this.broadcastInfo()
|
|
6202
|
+
},
|
|
6203
|
+
source
|
|
6204
|
+
);
|
|
5786
6205
|
}
|
|
5787
|
-
/**
|
|
5788
|
-
* Handle debug request - calls custom callback or broadcasts to components.
|
|
5789
|
-
*
|
|
5790
|
-
* When called from signal handlers, the Promise executes but return values
|
|
5791
|
-
* are not accessible due to Node.js signal handler constraints.
|
|
5792
|
-
*
|
|
5793
|
-
* @param source - Whether triggered from signal manager or manual trigger
|
|
5794
|
-
*/
|
|
5795
6206
|
async handleDebugRequest(source = "trigger") {
|
|
5796
|
-
this.
|
|
5797
|
-
|
|
5798
|
-
if (this.onDebugRequested) {
|
|
5799
|
-
const broadcastFn = () => this.broadcastDebug();
|
|
5800
|
-
const result = this.onDebugRequested(broadcastFn);
|
|
5801
|
-
if (isPromise(result)) {
|
|
5802
|
-
await result;
|
|
5803
|
-
}
|
|
5804
|
-
return {
|
|
6207
|
+
return this.handleSignalRequest(
|
|
6208
|
+
{
|
|
5805
6209
|
signal: "debug",
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
6210
|
+
dispatchedLogLabel: "Debug dispatched",
|
|
6211
|
+
emitSignal: () => this.lifecycleEvents.signalDebug(),
|
|
6212
|
+
customCallback: this.onDebugRequested,
|
|
6213
|
+
broadcast: () => this.broadcastDebug()
|
|
6214
|
+
},
|
|
6215
|
+
source
|
|
6216
|
+
);
|
|
5812
6217
|
}
|
|
5813
6218
|
/**
|
|
5814
|
-
*
|
|
5815
|
-
*
|
|
5816
|
-
*
|
|
6219
|
+
* Shared signal broadcast pipeline used by reload/info/debug.
|
|
6220
|
+
* Iterates running components, runs the picked handler with timeout, and
|
|
6221
|
+
* aggregates per-component results into a SignalBroadcastResult.
|
|
5817
6222
|
*/
|
|
5818
|
-
async
|
|
6223
|
+
async runSignalBroadcast(descriptor) {
|
|
5819
6224
|
const results = [];
|
|
5820
|
-
const
|
|
6225
|
+
const targets = this.components.filter(
|
|
5821
6226
|
(component) => this.runningComponents.has(component.getName())
|
|
5822
6227
|
);
|
|
5823
6228
|
if (this.isStarting) {
|
|
5824
|
-
this.logger.info(
|
|
5825
|
-
"Reload during startup: only reloading already-started components"
|
|
5826
|
-
);
|
|
6229
|
+
this.logger.info(descriptor.startupLog);
|
|
5827
6230
|
}
|
|
5828
|
-
for (const component of
|
|
6231
|
+
for (const component of targets) {
|
|
5829
6232
|
const name = component.getName();
|
|
5830
|
-
|
|
6233
|
+
const handler = descriptor.pickHandler(component);
|
|
6234
|
+
if (!handler) {
|
|
5831
6235
|
results.push({
|
|
5832
6236
|
name,
|
|
5833
6237
|
called: false,
|
|
@@ -5837,13 +6241,13 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
5837
6241
|
});
|
|
5838
6242
|
continue;
|
|
5839
6243
|
}
|
|
5840
|
-
|
|
6244
|
+
descriptor.emitStarted(name);
|
|
5841
6245
|
const timeoutMS = component.signalTimeoutMS;
|
|
5842
6246
|
let timeoutHandle;
|
|
5843
6247
|
const timeoutResult = { timedOut: true };
|
|
5844
6248
|
try {
|
|
5845
|
-
const
|
|
5846
|
-
const handlerPromise = isPromise(
|
|
6249
|
+
const handlerResult = handler();
|
|
6250
|
+
const handlerPromise = isPromise(handlerResult) ? handlerResult : Promise.resolve(handlerResult);
|
|
5847
6251
|
const outcome = timeoutMS > 0 ? await Promise.race([
|
|
5848
6252
|
handlerPromise,
|
|
5849
6253
|
new Promise((resolve) => {
|
|
@@ -5853,7 +6257,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
5853
6257
|
})
|
|
5854
6258
|
]) : await handlerPromise;
|
|
5855
6259
|
if (outcome === timeoutResult) {
|
|
5856
|
-
this.logger.entity(name).warn(
|
|
6260
|
+
this.logger.entity(name).warn(descriptor.timeoutLog, {
|
|
5857
6261
|
params: { timeoutMS }
|
|
5858
6262
|
});
|
|
5859
6263
|
Promise.resolve(handlerPromise).catch(() => {
|
|
@@ -5866,7 +6270,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
5866
6270
|
code: "timeout"
|
|
5867
6271
|
});
|
|
5868
6272
|
} else {
|
|
5869
|
-
|
|
6273
|
+
descriptor.emitCompleted(name);
|
|
5870
6274
|
results.push({
|
|
5871
6275
|
name,
|
|
5872
6276
|
called: true,
|
|
@@ -5877,10 +6281,10 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
5877
6281
|
}
|
|
5878
6282
|
} catch (error) {
|
|
5879
6283
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
5880
|
-
this.logger.entity(name).error(
|
|
6284
|
+
this.logger.entity(name).error(descriptor.errorLog, {
|
|
5881
6285
|
params: { error: err }
|
|
5882
6286
|
});
|
|
5883
|
-
|
|
6287
|
+
descriptor.emitFailed(name, err);
|
|
5884
6288
|
results.push({
|
|
5885
6289
|
name,
|
|
5886
6290
|
called: true,
|
|
@@ -5901,108 +6305,45 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
5901
6305
|
const isAllTimeout = calledResults.length > 0 && calledResults.every((result) => result.timedOut);
|
|
5902
6306
|
const code = hasError ? isAllError ? "error" : "partial_error" : hasTimeout ? isAllTimeout ? "timeout" : "partial_timeout" : "ok";
|
|
5903
6307
|
return {
|
|
5904
|
-
signal:
|
|
6308
|
+
signal: descriptor.signal,
|
|
5905
6309
|
results,
|
|
5906
6310
|
timedOut: hasTimeout,
|
|
5907
6311
|
code
|
|
5908
6312
|
};
|
|
5909
6313
|
}
|
|
6314
|
+
/**
|
|
6315
|
+
* Broadcast reload signal to all running components.
|
|
6316
|
+
* Calls onReload() on components that implement it.
|
|
6317
|
+
* Continues on errors - collects all results.
|
|
6318
|
+
*/
|
|
6319
|
+
async broadcastReload() {
|
|
6320
|
+
return this.runSignalBroadcast({
|
|
6321
|
+
signal: "reload",
|
|
6322
|
+
pickHandler: (component) => component.onReload?.bind(component),
|
|
6323
|
+
startupLog: "Reload during startup: only reloading already-started components",
|
|
6324
|
+
timeoutLog: "Reload handler timed out",
|
|
6325
|
+
errorLog: "Reload failed: {{error.message}}",
|
|
6326
|
+
emitStarted: (name) => this.lifecycleEvents.componentReloadStarted(name),
|
|
6327
|
+
emitCompleted: (name) => this.lifecycleEvents.componentReloadCompleted(name),
|
|
6328
|
+
emitFailed: (name, error) => this.lifecycleEvents.componentReloadFailed(name, error)
|
|
6329
|
+
});
|
|
6330
|
+
}
|
|
5910
6331
|
/**
|
|
5911
6332
|
* Broadcast info signal to all running components.
|
|
5912
6333
|
* Calls onInfo() on components that implement it.
|
|
5913
6334
|
* Continues on errors - collects all results.
|
|
5914
6335
|
*/
|
|
5915
6336
|
async broadcastInfo() {
|
|
5916
|
-
|
|
5917
|
-
const componentsToNotify = this.components.filter(
|
|
5918
|
-
(component) => this.runningComponents.has(component.getName())
|
|
5919
|
-
);
|
|
5920
|
-
if (this.isStarting) {
|
|
5921
|
-
this.logger.info(
|
|
5922
|
-
"Info during startup: only notifying already-started components"
|
|
5923
|
-
);
|
|
5924
|
-
}
|
|
5925
|
-
for (const component of componentsToNotify) {
|
|
5926
|
-
const name = component.getName();
|
|
5927
|
-
if (!component.onInfo) {
|
|
5928
|
-
results.push({
|
|
5929
|
-
name,
|
|
5930
|
-
called: false,
|
|
5931
|
-
error: null,
|
|
5932
|
-
timedOut: false,
|
|
5933
|
-
code: "no_handler"
|
|
5934
|
-
});
|
|
5935
|
-
continue;
|
|
5936
|
-
}
|
|
5937
|
-
this.lifecycleEvents.componentInfoStarted(name);
|
|
5938
|
-
const timeoutMS = component.signalTimeoutMS;
|
|
5939
|
-
let timeoutHandle;
|
|
5940
|
-
const timeoutResult = { timedOut: true };
|
|
5941
|
-
try {
|
|
5942
|
-
const result = component.onInfo();
|
|
5943
|
-
const handlerPromise = isPromise(result) ? result : Promise.resolve(result);
|
|
5944
|
-
const outcome = timeoutMS > 0 ? await Promise.race([
|
|
5945
|
-
handlerPromise,
|
|
5946
|
-
new Promise((resolve) => {
|
|
5947
|
-
timeoutHandle = setTimeout(() => {
|
|
5948
|
-
resolve(timeoutResult);
|
|
5949
|
-
}, timeoutMS);
|
|
5950
|
-
})
|
|
5951
|
-
]) : await handlerPromise;
|
|
5952
|
-
if (outcome === timeoutResult) {
|
|
5953
|
-
this.logger.entity(name).warn("Info handler timed out", {
|
|
5954
|
-
params: { timeoutMS }
|
|
5955
|
-
});
|
|
5956
|
-
Promise.resolve(handlerPromise).catch(() => {
|
|
5957
|
-
});
|
|
5958
|
-
results.push({
|
|
5959
|
-
name,
|
|
5960
|
-
called: true,
|
|
5961
|
-
error: null,
|
|
5962
|
-
timedOut: true,
|
|
5963
|
-
code: "timeout"
|
|
5964
|
-
});
|
|
5965
|
-
} else {
|
|
5966
|
-
this.lifecycleEvents.componentInfoCompleted(name);
|
|
5967
|
-
results.push({
|
|
5968
|
-
name,
|
|
5969
|
-
called: true,
|
|
5970
|
-
error: null,
|
|
5971
|
-
timedOut: false,
|
|
5972
|
-
code: "called"
|
|
5973
|
-
});
|
|
5974
|
-
}
|
|
5975
|
-
} catch (error) {
|
|
5976
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
5977
|
-
this.logger.entity(name).error("Info handler failed: {{error.message}}", {
|
|
5978
|
-
params: { error: err }
|
|
5979
|
-
});
|
|
5980
|
-
this.lifecycleEvents.componentInfoFailed(name, err);
|
|
5981
|
-
results.push({
|
|
5982
|
-
name,
|
|
5983
|
-
called: true,
|
|
5984
|
-
error: err,
|
|
5985
|
-
timedOut: false,
|
|
5986
|
-
code: "error"
|
|
5987
|
-
});
|
|
5988
|
-
} finally {
|
|
5989
|
-
if (timeoutHandle) {
|
|
5990
|
-
clearTimeout(timeoutHandle);
|
|
5991
|
-
}
|
|
5992
|
-
}
|
|
5993
|
-
}
|
|
5994
|
-
const calledResults = results.filter((result) => result.called);
|
|
5995
|
-
const hasError = calledResults.some((result) => result.error);
|
|
5996
|
-
const isAllError = calledResults.length > 0 && calledResults.every((result) => result.error);
|
|
5997
|
-
const hasTimeout = calledResults.some((result) => result.timedOut);
|
|
5998
|
-
const isAllTimeout = calledResults.length > 0 && calledResults.every((result) => result.timedOut);
|
|
5999
|
-
const code = hasError ? isAllError ? "error" : "partial_error" : hasTimeout ? isAllTimeout ? "timeout" : "partial_timeout" : "ok";
|
|
6000
|
-
return {
|
|
6337
|
+
return this.runSignalBroadcast({
|
|
6001
6338
|
signal: "info",
|
|
6002
|
-
|
|
6003
|
-
|
|
6004
|
-
|
|
6005
|
-
|
|
6339
|
+
pickHandler: (component) => component.onInfo?.bind(component),
|
|
6340
|
+
startupLog: "Info during startup: only notifying already-started components",
|
|
6341
|
+
timeoutLog: "Info handler timed out",
|
|
6342
|
+
errorLog: "Info handler failed: {{error.message}}",
|
|
6343
|
+
emitStarted: (name) => this.lifecycleEvents.componentInfoStarted(name),
|
|
6344
|
+
emitCompleted: (name) => this.lifecycleEvents.componentInfoCompleted(name),
|
|
6345
|
+
emitFailed: (name, error) => this.lifecycleEvents.componentInfoFailed(name, error)
|
|
6346
|
+
});
|
|
6006
6347
|
}
|
|
6007
6348
|
/**
|
|
6008
6349
|
* Broadcast debug signal to all running components.
|
|
@@ -6010,96 +6351,16 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
6010
6351
|
* Continues on errors - collects all results.
|
|
6011
6352
|
*/
|
|
6012
6353
|
async broadcastDebug() {
|
|
6013
|
-
|
|
6014
|
-
const componentsToNotify = this.components.filter(
|
|
6015
|
-
(component) => this.runningComponents.has(component.getName())
|
|
6016
|
-
);
|
|
6017
|
-
if (this.isStarting) {
|
|
6018
|
-
this.logger.info(
|
|
6019
|
-
"Debug during startup: only notifying already-started components"
|
|
6020
|
-
);
|
|
6021
|
-
}
|
|
6022
|
-
for (const component of componentsToNotify) {
|
|
6023
|
-
const name = component.getName();
|
|
6024
|
-
if (!component.onDebug) {
|
|
6025
|
-
results.push({
|
|
6026
|
-
name,
|
|
6027
|
-
called: false,
|
|
6028
|
-
error: null,
|
|
6029
|
-
timedOut: false,
|
|
6030
|
-
code: "no_handler"
|
|
6031
|
-
});
|
|
6032
|
-
continue;
|
|
6033
|
-
}
|
|
6034
|
-
this.lifecycleEvents.componentDebugStarted(name);
|
|
6035
|
-
const timeoutMS = component.signalTimeoutMS;
|
|
6036
|
-
let timeoutHandle;
|
|
6037
|
-
const timeoutResult = { timedOut: true };
|
|
6038
|
-
try {
|
|
6039
|
-
const result = component.onDebug();
|
|
6040
|
-
const handlerPromise = isPromise(result) ? result : Promise.resolve(result);
|
|
6041
|
-
const outcome = timeoutMS > 0 ? await Promise.race([
|
|
6042
|
-
handlerPromise,
|
|
6043
|
-
new Promise((resolve) => {
|
|
6044
|
-
timeoutHandle = setTimeout(() => {
|
|
6045
|
-
resolve(timeoutResult);
|
|
6046
|
-
}, timeoutMS);
|
|
6047
|
-
})
|
|
6048
|
-
]) : await handlerPromise;
|
|
6049
|
-
if (outcome === timeoutResult) {
|
|
6050
|
-
this.logger.entity(name).warn("Debug handler timed out", {
|
|
6051
|
-
params: { timeoutMS }
|
|
6052
|
-
});
|
|
6053
|
-
Promise.resolve(handlerPromise).catch(() => {
|
|
6054
|
-
});
|
|
6055
|
-
results.push({
|
|
6056
|
-
name,
|
|
6057
|
-
called: true,
|
|
6058
|
-
error: null,
|
|
6059
|
-
timedOut: true,
|
|
6060
|
-
code: "timeout"
|
|
6061
|
-
});
|
|
6062
|
-
} else {
|
|
6063
|
-
this.lifecycleEvents.componentDebugCompleted(name);
|
|
6064
|
-
results.push({
|
|
6065
|
-
name,
|
|
6066
|
-
called: true,
|
|
6067
|
-
error: null,
|
|
6068
|
-
timedOut: false,
|
|
6069
|
-
code: "called"
|
|
6070
|
-
});
|
|
6071
|
-
}
|
|
6072
|
-
} catch (error) {
|
|
6073
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
6074
|
-
this.logger.entity(name).error("Debug handler failed: {{error.message}}", {
|
|
6075
|
-
params: { error: err }
|
|
6076
|
-
});
|
|
6077
|
-
this.lifecycleEvents.componentDebugFailed(name, err);
|
|
6078
|
-
results.push({
|
|
6079
|
-
name,
|
|
6080
|
-
called: true,
|
|
6081
|
-
error: err,
|
|
6082
|
-
timedOut: false,
|
|
6083
|
-
code: "error"
|
|
6084
|
-
});
|
|
6085
|
-
} finally {
|
|
6086
|
-
if (timeoutHandle) {
|
|
6087
|
-
clearTimeout(timeoutHandle);
|
|
6088
|
-
}
|
|
6089
|
-
}
|
|
6090
|
-
}
|
|
6091
|
-
const calledResults = results.filter((result) => result.called);
|
|
6092
|
-
const hasError = calledResults.some((result) => result.error);
|
|
6093
|
-
const isAllError = calledResults.length > 0 && calledResults.every((result) => result.error);
|
|
6094
|
-
const hasTimeout = calledResults.some((result) => result.timedOut);
|
|
6095
|
-
const isAllTimeout = calledResults.length > 0 && calledResults.every((result) => result.timedOut);
|
|
6096
|
-
const code = hasError ? isAllError ? "error" : "partial_error" : hasTimeout ? isAllTimeout ? "timeout" : "partial_timeout" : "ok";
|
|
6097
|
-
return {
|
|
6354
|
+
return this.runSignalBroadcast({
|
|
6098
6355
|
signal: "debug",
|
|
6099
|
-
|
|
6100
|
-
|
|
6101
|
-
|
|
6102
|
-
|
|
6356
|
+
pickHandler: (component) => component.onDebug?.bind(component),
|
|
6357
|
+
startupLog: "Debug during startup: only notifying already-started components",
|
|
6358
|
+
timeoutLog: "Debug handler timed out",
|
|
6359
|
+
errorLog: "Debug handler failed: {{error.message}}",
|
|
6360
|
+
emitStarted: (name) => this.lifecycleEvents.componentDebugStarted(name),
|
|
6361
|
+
emitCompleted: (name) => this.lifecycleEvents.componentDebugCompleted(name),
|
|
6362
|
+
emitFailed: (name, error) => this.lifecycleEvents.componentDebugFailed(name, error)
|
|
6363
|
+
});
|
|
6103
6364
|
}
|
|
6104
6365
|
};
|
|
6105
6366
|
|
|
@@ -6125,6 +6386,10 @@ var BaseComponent = class {
|
|
|
6125
6386
|
name;
|
|
6126
6387
|
/** Reference to component-scoped lifecycle (set by manager when registered) */
|
|
6127
6388
|
lifecycle;
|
|
6389
|
+
/** @internal Set by LifecycleManager while the component is running. */
|
|
6390
|
+
_unexpectedStopHandler;
|
|
6391
|
+
/** @internal Incremented whenever the unexpected-stop handler is re-armed or cleared. */
|
|
6392
|
+
_unexpectedStopGeneration = 0;
|
|
6128
6393
|
/**
|
|
6129
6394
|
* Create a new component
|
|
6130
6395
|
*
|
|
@@ -6159,6 +6424,24 @@ var BaseComponent = class {
|
|
|
6159
6424
|
// Default if undefined/null/non-finite
|
|
6160
6425
|
);
|
|
6161
6426
|
}
|
|
6427
|
+
/** @internal Called by LifecycleManager after a successful start. */
|
|
6428
|
+
_setUnexpectedStopHandler(handler) {
|
|
6429
|
+
this._unexpectedStopGeneration += 1;
|
|
6430
|
+
this._unexpectedStopHandler = handler;
|
|
6431
|
+
const generation = this._unexpectedStopGeneration;
|
|
6432
|
+
this.reportUnexpectedStop = (error) => {
|
|
6433
|
+
if (this._unexpectedStopGeneration !== generation) {
|
|
6434
|
+
return false;
|
|
6435
|
+
}
|
|
6436
|
+
return this._unexpectedStopHandler?.(error) ?? false;
|
|
6437
|
+
};
|
|
6438
|
+
}
|
|
6439
|
+
/** @internal Called by LifecycleManager when stop begins or component is unregistered. */
|
|
6440
|
+
_clearUnexpectedStopHandler() {
|
|
6441
|
+
this._unexpectedStopGeneration += 1;
|
|
6442
|
+
this._unexpectedStopHandler = void 0;
|
|
6443
|
+
this.reportUnexpectedStop = () => false;
|
|
6444
|
+
}
|
|
6162
6445
|
/**
|
|
6163
6446
|
* Get component name
|
|
6164
6447
|
*/
|
|
@@ -6177,6 +6460,38 @@ var BaseComponent = class {
|
|
|
6177
6460
|
isOptional() {
|
|
6178
6461
|
return this.optional;
|
|
6179
6462
|
}
|
|
6463
|
+
/**
|
|
6464
|
+
* Run-scoped unexpected-stop callback. Rebound by LifecycleManager on each
|
|
6465
|
+
* successful start so captured references from older runs go stale.
|
|
6466
|
+
*/
|
|
6467
|
+
reportUnexpectedStop = () => false;
|
|
6468
|
+
/**
|
|
6469
|
+
* Get this component's own status from the manager's perspective.
|
|
6470
|
+
*
|
|
6471
|
+
* Equivalent to `this.lifecycle.getComponentStatus(this.getName())` but without
|
|
6472
|
+
* needing to pass the name. Returns `undefined` if the component is not registered.
|
|
6473
|
+
*
|
|
6474
|
+
* Check `status?.state === 'running'` to test whether the component is currently running.
|
|
6475
|
+
*/
|
|
6476
|
+
getSelfStatus() {
|
|
6477
|
+
return this.lifecycle?.getComponentStatus(this.name);
|
|
6478
|
+
}
|
|
6479
|
+
/**
|
|
6480
|
+
* Capture a run-scoped unexpected-stop reporter for async listeners created during start().
|
|
6481
|
+
*
|
|
6482
|
+
* Unlike calling `this.reportUnexpectedStop()` later, the returned callback becomes a no-op
|
|
6483
|
+
* once the component is stopped, unregistered, or restarted. This prevents stale listeners
|
|
6484
|
+
* from a previous run from stopping a newer run of the same component instance.
|
|
6485
|
+
*/
|
|
6486
|
+
getUnexpectedStopReporter() {
|
|
6487
|
+
const generation = this._unexpectedStopGeneration;
|
|
6488
|
+
return (error) => {
|
|
6489
|
+
if (this._unexpectedStopGeneration !== generation) {
|
|
6490
|
+
return false;
|
|
6491
|
+
}
|
|
6492
|
+
return this._unexpectedStopHandler?.(error) ?? false;
|
|
6493
|
+
};
|
|
6494
|
+
}
|
|
6180
6495
|
};
|
|
6181
6496
|
// Annotate the CommonJS export names for ESM import in node:
|
|
6182
6497
|
0 && (module.exports = {
|