lifecycleion 0.0.13 → 0.0.15
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 +666 -329
- package/dist/lib/lifecycle-manager/index.cjs.map +1 -1
- package/dist/lib/lifecycle-manager/index.d.cts +116 -27
- package/dist/lib/lifecycle-manager/index.d.ts +116 -27
- package/dist/lib/lifecycle-manager/index.js +666 -329
- 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,26 @@ 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
|
+
var LIFECYCLE_MANAGER_MESSAGE_DUPLICATE_COMPONENT_INSTANCE_EXTERNAL = "Component instance is already registered with another lifecycle manager.";
|
|
1481
|
+
|
|
1452
1482
|
// src/lib/process-signal-manager.ts
|
|
1453
1483
|
var import_ulid = require("ulid");
|
|
1454
1484
|
var import_readline = __toESM(require("readline"), 1);
|
|
@@ -1993,8 +2023,15 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
1993
2023
|
componentTimestamps = /* @__PURE__ */ new Map();
|
|
1994
2024
|
componentErrors = /* @__PURE__ */ new Map();
|
|
1995
2025
|
componentStartAttemptTokens = /* @__PURE__ */ new Map();
|
|
2026
|
+
// Use per-stop ULIDs instead of incrementing counters because a stalled
|
|
2027
|
+
// component can be unregistered and replaced by a same-name instance before
|
|
2028
|
+
// the old floating stop promise settles.
|
|
2029
|
+
componentStopAttemptTokens = /* @__PURE__ */ new Map();
|
|
2030
|
+
pendingForceStopWaiters = /* @__PURE__ */ new Map();
|
|
2031
|
+
unexpectedStopsDuringStartup = /* @__PURE__ */ new Map();
|
|
1996
2032
|
// State flags
|
|
1997
2033
|
isStarting = false;
|
|
2034
|
+
autoAttachedSignalsDuringStartup = false;
|
|
1998
2035
|
isStarted = false;
|
|
1999
2036
|
isShuttingDown = false;
|
|
2000
2037
|
// Unique token used to detect shutdowns that happened during async start().
|
|
@@ -2143,7 +2180,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2143
2180
|
*/
|
|
2144
2181
|
async unregisterComponent(name, options) {
|
|
2145
2182
|
if (this.isStarting || this.isShuttingDown) {
|
|
2146
|
-
this.logger.entity(name).warn(
|
|
2183
|
+
this.logger.entity(name).warn(LIFECYCLE_MANAGER_MESSAGE_BULK_OPERATION_IN_PROGRESS, {
|
|
2147
2184
|
params: {
|
|
2148
2185
|
isStarting: this.isStarting,
|
|
2149
2186
|
isShuttingDown: this.isShuttingDown
|
|
@@ -2152,7 +2189,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2152
2189
|
return {
|
|
2153
2190
|
success: false,
|
|
2154
2191
|
componentName: name,
|
|
2155
|
-
reason:
|
|
2192
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_BULK_OPERATION_IN_PROGRESS,
|
|
2156
2193
|
code: "bulk_operation_in_progress",
|
|
2157
2194
|
wasStopped: false,
|
|
2158
2195
|
wasRegistered: this.hasComponent(name)
|
|
@@ -2160,11 +2197,11 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2160
2197
|
}
|
|
2161
2198
|
const component = this.getComponent(name);
|
|
2162
2199
|
if (!component) {
|
|
2163
|
-
this.logger.entity(name).warn(
|
|
2200
|
+
this.logger.entity(name).warn(LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_FOUND);
|
|
2164
2201
|
return {
|
|
2165
2202
|
success: false,
|
|
2166
2203
|
componentName: name,
|
|
2167
|
-
reason:
|
|
2204
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_FOUND,
|
|
2168
2205
|
code: "component_not_found",
|
|
2169
2206
|
wasStopped: false,
|
|
2170
2207
|
wasRegistered: false
|
|
@@ -2177,7 +2214,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2177
2214
|
return {
|
|
2178
2215
|
success: false,
|
|
2179
2216
|
componentName: name,
|
|
2180
|
-
reason:
|
|
2217
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_STALLED,
|
|
2181
2218
|
code: "stop_failed",
|
|
2182
2219
|
stopFailureReason: "stalled",
|
|
2183
2220
|
wasStopped: false,
|
|
@@ -2229,10 +2266,14 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2229
2266
|
wasStopped = true;
|
|
2230
2267
|
}
|
|
2231
2268
|
this.components = this.components.filter((c) => c.getName() !== name);
|
|
2269
|
+
component._clearUnexpectedStopHandler();
|
|
2270
|
+
component._markUnregistered();
|
|
2232
2271
|
this.componentStates.delete(name);
|
|
2233
2272
|
this.componentTimestamps.delete(name);
|
|
2234
2273
|
this.componentErrors.delete(name);
|
|
2235
2274
|
this.componentStartAttemptTokens.delete(name);
|
|
2275
|
+
this.componentStopAttemptTokens.delete(name);
|
|
2276
|
+
this.pendingForceStopWaiters.delete(name);
|
|
2236
2277
|
this.stalledComponents.delete(name);
|
|
2237
2278
|
this.runningComponents.delete(name);
|
|
2238
2279
|
this.updateStartedFlag();
|
|
@@ -2579,7 +2620,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2579
2620
|
startedComponents: [],
|
|
2580
2621
|
failedOptionalComponents: [],
|
|
2581
2622
|
skippedDueToDependency: [],
|
|
2582
|
-
reason:
|
|
2623
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_SHUTDOWN_IN_PROGRESS,
|
|
2583
2624
|
code: "shutdown_in_progress",
|
|
2584
2625
|
durationMS: Date.now() - startTime
|
|
2585
2626
|
};
|
|
@@ -2639,6 +2680,8 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2639
2680
|
};
|
|
2640
2681
|
}
|
|
2641
2682
|
this.isStarting = true;
|
|
2683
|
+
this.autoAttachedSignalsDuringStartup = false;
|
|
2684
|
+
this.unexpectedStopsDuringStartup.clear();
|
|
2642
2685
|
this.resetRepeatedShutdownRequestState();
|
|
2643
2686
|
this.shutdownMethod = null;
|
|
2644
2687
|
this.lastShutdownResult = null;
|
|
@@ -2771,13 +2814,49 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2771
2814
|
error: result.error,
|
|
2772
2815
|
durationMS: Date.now() - startTime
|
|
2773
2816
|
};
|
|
2817
|
+
} else if (result.code === "component_unexpected_stop") {
|
|
2818
|
+
this.unexpectedStopsDuringStartup.delete(name);
|
|
2819
|
+
const error = result.error || new Error(
|
|
2820
|
+
result.reason || `Component "${name}" stopped unexpectedly`
|
|
2821
|
+
);
|
|
2822
|
+
if (component.isOptional()) {
|
|
2823
|
+
if (!failedOptionalComponents.some((entry) => entry.name === name)) {
|
|
2824
|
+
failedOptionalComponents.push({ name, error });
|
|
2825
|
+
}
|
|
2826
|
+
this.logger.entity(name).warn(
|
|
2827
|
+
LIFECYCLE_MANAGER_LOG_OPTIONAL_COMPONENT_UNEXPECTED_STOP_DURING_STARTUP,
|
|
2828
|
+
{
|
|
2829
|
+
params: { error }
|
|
2830
|
+
}
|
|
2831
|
+
);
|
|
2832
|
+
} else {
|
|
2833
|
+
this.logger.entity(name).error(
|
|
2834
|
+
LIFECYCLE_MANAGER_LOG_REQUIRED_COMPONENT_UNEXPECTED_STOP_DURING_STARTUP,
|
|
2835
|
+
{
|
|
2836
|
+
params: { error }
|
|
2837
|
+
}
|
|
2838
|
+
);
|
|
2839
|
+
await this.rollbackStartup(startedComponents);
|
|
2840
|
+
return {
|
|
2841
|
+
success: false,
|
|
2842
|
+
startedComponents: [],
|
|
2843
|
+
failedOptionalComponents,
|
|
2844
|
+
skippedDueToDependency: Array.from(skippedDueToDependency),
|
|
2845
|
+
reason: error.message,
|
|
2846
|
+
code: "component_unexpected_stop",
|
|
2847
|
+
error,
|
|
2848
|
+
durationMS: Date.now() - startTime
|
|
2849
|
+
};
|
|
2850
|
+
}
|
|
2774
2851
|
} else {
|
|
2775
2852
|
if (component.isOptional()) {
|
|
2776
2853
|
this.logger.entity(name).warn(
|
|
2777
2854
|
"Optional component failed to start, continuing: {{error.message}}",
|
|
2778
2855
|
{
|
|
2779
2856
|
params: {
|
|
2780
|
-
error: result.error || new Error(
|
|
2857
|
+
error: result.error || new Error(
|
|
2858
|
+
result.reason || LIFECYCLE_MANAGER_MESSAGE_UNKNOWN_ERROR
|
|
2859
|
+
)
|
|
2781
2860
|
}
|
|
2782
2861
|
}
|
|
2783
2862
|
);
|
|
@@ -2791,14 +2870,18 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2791
2870
|
}
|
|
2792
2871
|
failedOptionalComponents.push({
|
|
2793
2872
|
name,
|
|
2794
|
-
error: result.error || new Error(
|
|
2873
|
+
error: result.error || new Error(
|
|
2874
|
+
result.reason || LIFECYCLE_MANAGER_MESSAGE_UNKNOWN_ERROR
|
|
2875
|
+
)
|
|
2795
2876
|
});
|
|
2796
2877
|
} else {
|
|
2797
2878
|
this.logger.entity(name).error(
|
|
2798
2879
|
"Required component failed to start, rolling back: {{error.message}}",
|
|
2799
2880
|
{
|
|
2800
2881
|
params: {
|
|
2801
|
-
error: result.error || new Error(
|
|
2882
|
+
error: result.error || new Error(
|
|
2883
|
+
result.reason || LIFECYCLE_MANAGER_MESSAGE_UNKNOWN_ERROR
|
|
2884
|
+
)
|
|
2802
2885
|
}
|
|
2803
2886
|
}
|
|
2804
2887
|
);
|
|
@@ -2815,6 +2898,25 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2815
2898
|
};
|
|
2816
2899
|
}
|
|
2817
2900
|
}
|
|
2901
|
+
const unexpectedStopResult2 = this.consumeUnexpectedStopsDuringStartup(
|
|
2902
|
+
startedComponents,
|
|
2903
|
+
failedOptionalComponents
|
|
2904
|
+
);
|
|
2905
|
+
startedComponents.splice(0, startedComponents.length);
|
|
2906
|
+
startedComponents.push(...unexpectedStopResult2.startedComponents);
|
|
2907
|
+
if (unexpectedStopResult2.requiredFailure) {
|
|
2908
|
+
await this.rollbackStartup(startedComponents);
|
|
2909
|
+
return {
|
|
2910
|
+
success: false,
|
|
2911
|
+
startedComponents: [],
|
|
2912
|
+
failedOptionalComponents,
|
|
2913
|
+
skippedDueToDependency: Array.from(skippedDueToDependency),
|
|
2914
|
+
reason: unexpectedStopResult2.requiredFailure.error.message,
|
|
2915
|
+
code: "component_unexpected_stop",
|
|
2916
|
+
error: unexpectedStopResult2.requiredFailure.error,
|
|
2917
|
+
durationMS: Date.now() - startTime
|
|
2918
|
+
};
|
|
2919
|
+
}
|
|
2818
2920
|
}
|
|
2819
2921
|
if (hasTimedOut) {
|
|
2820
2922
|
const durationMS2 = Date.now() - startTime;
|
|
@@ -2838,6 +2940,25 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2838
2940
|
code: "startup_timeout"
|
|
2839
2941
|
};
|
|
2840
2942
|
}
|
|
2943
|
+
const unexpectedStopResult = this.consumeUnexpectedStopsDuringStartup(
|
|
2944
|
+
startedComponents,
|
|
2945
|
+
failedOptionalComponents
|
|
2946
|
+
);
|
|
2947
|
+
startedComponents.splice(0, startedComponents.length);
|
|
2948
|
+
startedComponents.push(...unexpectedStopResult.startedComponents);
|
|
2949
|
+
if (unexpectedStopResult.requiredFailure) {
|
|
2950
|
+
await this.rollbackStartup(startedComponents);
|
|
2951
|
+
return {
|
|
2952
|
+
success: false,
|
|
2953
|
+
startedComponents: [],
|
|
2954
|
+
failedOptionalComponents,
|
|
2955
|
+
skippedDueToDependency: Array.from(skippedDueToDependency),
|
|
2956
|
+
reason: unexpectedStopResult.requiredFailure.error.message,
|
|
2957
|
+
code: "component_unexpected_stop",
|
|
2958
|
+
error: unexpectedStopResult.requiredFailure.error,
|
|
2959
|
+
durationMS: Date.now() - startTime
|
|
2960
|
+
};
|
|
2961
|
+
}
|
|
2841
2962
|
this.updateStartedFlag();
|
|
2842
2963
|
const skippedComponentsArray = [
|
|
2843
2964
|
...Array.from(skippedDueToDependency),
|
|
@@ -2869,10 +2990,12 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2869
2990
|
if (timeoutHandle) {
|
|
2870
2991
|
clearTimeout(timeoutHandle);
|
|
2871
2992
|
}
|
|
2872
|
-
|
|
2993
|
+
this.isStarting = false;
|
|
2994
|
+
if (didAutoAttachSignalsForBulkStartup || this.autoAttachedSignalsDuringStartup) {
|
|
2873
2995
|
this.autoDetachSignalsIfIdle("failed bulk startup");
|
|
2874
2996
|
}
|
|
2875
|
-
this.
|
|
2997
|
+
this.autoAttachedSignalsDuringStartup = false;
|
|
2998
|
+
this.unexpectedStopsDuringStartup.clear();
|
|
2876
2999
|
}
|
|
2877
3000
|
}
|
|
2878
3001
|
/**
|
|
@@ -2936,7 +3059,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2936
3059
|
return {
|
|
2937
3060
|
success: false,
|
|
2938
3061
|
componentName: name,
|
|
2939
|
-
reason:
|
|
3062
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_BULK_STARTUP_IN_PROGRESS,
|
|
2940
3063
|
code: "startup_in_progress"
|
|
2941
3064
|
};
|
|
2942
3065
|
}
|
|
@@ -2947,7 +3070,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2947
3070
|
return {
|
|
2948
3071
|
success: false,
|
|
2949
3072
|
componentName: name,
|
|
2950
|
-
reason:
|
|
3073
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_SHUTDOWN_IN_PROGRESS,
|
|
2951
3074
|
code: "shutdown_in_progress"
|
|
2952
3075
|
};
|
|
2953
3076
|
}
|
|
@@ -2981,7 +3104,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2981
3104
|
return {
|
|
2982
3105
|
success: false,
|
|
2983
3106
|
componentName: name,
|
|
2984
|
-
reason: this.isStarting ?
|
|
3107
|
+
reason: this.isStarting ? LIFECYCLE_MANAGER_MESSAGE_BULK_STARTUP_IN_PROGRESS : LIFECYCLE_MANAGER_MESSAGE_SHUTDOWN_IN_PROGRESS,
|
|
2985
3108
|
code: this.isStarting ? "startup_in_progress" : "shutdown_in_progress"
|
|
2986
3109
|
};
|
|
2987
3110
|
}
|
|
@@ -3163,7 +3286,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3163
3286
|
if (this.isShuttingDown) {
|
|
3164
3287
|
if (isFirstExit && this.pendingLoggerExitResolve === null) {
|
|
3165
3288
|
this.logger.debug(
|
|
3166
|
-
|
|
3289
|
+
LIFECYCLE_MANAGER_LOG_LOGGER_EXIT_DURING_SHUTDOWN,
|
|
3167
3290
|
{
|
|
3168
3291
|
params: { exitCode }
|
|
3169
3292
|
}
|
|
@@ -3172,7 +3295,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3172
3295
|
this.pendingLoggerExitResolve = resolve;
|
|
3173
3296
|
});
|
|
3174
3297
|
}
|
|
3175
|
-
this.logger.debug(
|
|
3298
|
+
this.logger.debug(LIFECYCLE_MANAGER_LOG_LOGGER_EXIT_DURING_SHUTDOWN, {
|
|
3176
3299
|
params: { exitCode }
|
|
3177
3300
|
});
|
|
3178
3301
|
return { action: "wait" };
|
|
@@ -3262,7 +3385,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3262
3385
|
return {
|
|
3263
3386
|
name,
|
|
3264
3387
|
healthy: false,
|
|
3265
|
-
message:
|
|
3388
|
+
message: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_FOUND,
|
|
3266
3389
|
checkedAt: startTime,
|
|
3267
3390
|
durationMS: 0,
|
|
3268
3391
|
error: null,
|
|
@@ -3275,7 +3398,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3275
3398
|
return {
|
|
3276
3399
|
name,
|
|
3277
3400
|
healthy: false,
|
|
3278
|
-
message: isStalled ?
|
|
3401
|
+
message: isStalled ? LIFECYCLE_MANAGER_MESSAGE_COMPONENT_STALLED : LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_RUNNING,
|
|
3279
3402
|
checkedAt: startTime,
|
|
3280
3403
|
durationMS: Date.now() - startTime,
|
|
3281
3404
|
error: null,
|
|
@@ -3491,7 +3614,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3491
3614
|
result = component.onMessage(payload, from);
|
|
3492
3615
|
} catch (error) {
|
|
3493
3616
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
3494
|
-
this.logger.entity(componentName).error(
|
|
3617
|
+
this.logger.entity(componentName).error(LIFECYCLE_MANAGER_LOG_MESSAGE_HANDLER_FAILED, {
|
|
3495
3618
|
params: { error: err, from }
|
|
3496
3619
|
});
|
|
3497
3620
|
this.lifecycleEvents.componentMessageFailed(componentName, from, err, {
|
|
@@ -3551,7 +3674,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3551
3674
|
};
|
|
3552
3675
|
} catch (error) {
|
|
3553
3676
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
3554
|
-
this.logger.entity(componentName).error(
|
|
3677
|
+
this.logger.entity(componentName).error(LIFECYCLE_MANAGER_LOG_MESSAGE_HANDLER_FAILED, {
|
|
3555
3678
|
params: { error: err, from, timeoutMS }
|
|
3556
3679
|
});
|
|
3557
3680
|
this.lifecycleEvents.componentMessageFailed(componentName, from, err, {
|
|
@@ -3824,7 +3947,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3824
3947
|
this.lifecycleEvents.componentRegistrationRejected({
|
|
3825
3948
|
name: componentName,
|
|
3826
3949
|
reason: "shutdown_in_progress",
|
|
3827
|
-
message:
|
|
3950
|
+
message: LIFECYCLE_MANAGER_MESSAGE_REGISTER_SHUTDOWN_IN_PROGRESS,
|
|
3828
3951
|
registrationIndexBefore,
|
|
3829
3952
|
registrationIndexAfter: registrationIndexBefore,
|
|
3830
3953
|
requestedPosition: isInsertAction ? { position, targetComponentName } : void 0,
|
|
@@ -3836,7 +3959,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3836
3959
|
targetComponentName,
|
|
3837
3960
|
registrationIndexBefore,
|
|
3838
3961
|
code: "shutdown_in_progress",
|
|
3839
|
-
reason:
|
|
3962
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_REGISTER_SHUTDOWN_IN_PROGRESS,
|
|
3840
3963
|
targetFound: void 0
|
|
3841
3964
|
});
|
|
3842
3965
|
}
|
|
@@ -3847,7 +3970,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3847
3970
|
this.lifecycleEvents.componentRegistrationRejected({
|
|
3848
3971
|
name: componentName,
|
|
3849
3972
|
reason: "startup_in_progress",
|
|
3850
|
-
message:
|
|
3973
|
+
message: LIFECYCLE_MANAGER_MESSAGE_REGISTER_REQUIRED_DEPENDENCY_DURING_STARTUP,
|
|
3851
3974
|
registrationIndexBefore,
|
|
3852
3975
|
registrationIndexAfter: registrationIndexBefore,
|
|
3853
3976
|
requestedPosition: isInsertAction ? { position, targetComponentName } : void 0,
|
|
@@ -3859,16 +3982,20 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3859
3982
|
targetComponentName,
|
|
3860
3983
|
registrationIndexBefore,
|
|
3861
3984
|
code: "startup_in_progress",
|
|
3862
|
-
reason:
|
|
3985
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_REGISTER_REQUIRED_DEPENDENCY_DURING_STARTUP,
|
|
3863
3986
|
targetFound: void 0
|
|
3864
3987
|
});
|
|
3865
3988
|
}
|
|
3866
|
-
if (
|
|
3867
|
-
this.
|
|
3989
|
+
if (component._isRegisteredWithManager()) {
|
|
3990
|
+
const isRegisteredHere = this.hasComponentInstance(component);
|
|
3991
|
+
const message = isRegisteredHere ? LIFECYCLE_MANAGER_MESSAGE_DUPLICATE_COMPONENT_INSTANCE : LIFECYCLE_MANAGER_MESSAGE_DUPLICATE_COMPONENT_INSTANCE_EXTERNAL;
|
|
3992
|
+
this.logger.entity(componentName).warn(
|
|
3993
|
+
isRegisteredHere ? "Component instance already registered" : "Component instance already registered with another lifecycle manager"
|
|
3994
|
+
);
|
|
3868
3995
|
this.lifecycleEvents.componentRegistrationRejected({
|
|
3869
3996
|
name: componentName,
|
|
3870
3997
|
reason: "duplicate_instance",
|
|
3871
|
-
message
|
|
3998
|
+
message,
|
|
3872
3999
|
registrationIndexBefore,
|
|
3873
4000
|
registrationIndexAfter: registrationIndexBefore,
|
|
3874
4001
|
requestedPosition: isInsertAction ? { position, targetComponentName } : void 0,
|
|
@@ -3880,7 +4007,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3880
4007
|
targetComponentName,
|
|
3881
4008
|
registrationIndexBefore,
|
|
3882
4009
|
code: "duplicate_instance",
|
|
3883
|
-
reason:
|
|
4010
|
+
reason: message,
|
|
3884
4011
|
targetFound: void 0
|
|
3885
4012
|
});
|
|
3886
4013
|
}
|
|
@@ -3993,6 +4120,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3993
4120
|
getValueInternal: (compName, key, from) => this.getValueInternal(compName, key, from)
|
|
3994
4121
|
};
|
|
3995
4122
|
component.lifecycle = new ComponentLifecycle(this, componentName, internalCallbacks);
|
|
4123
|
+
component._markRegistered();
|
|
3996
4124
|
this.componentStates.set(componentName, "registered");
|
|
3997
4125
|
this.componentTimestamps.set(componentName, {
|
|
3998
4126
|
startedAt: null,
|
|
@@ -4194,8 +4322,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4194
4322
|
const runningComponentsToStop = shutdownOrder.filter(
|
|
4195
4323
|
(name) => this.isComponentRunning(name) || shouldRetryStalled && stalledComponentNames.has(name)
|
|
4196
4324
|
);
|
|
4197
|
-
const stoppedComponents =
|
|
4198
|
-
const stalledComponents = [];
|
|
4325
|
+
const stoppedComponents = /* @__PURE__ */ new Set();
|
|
4199
4326
|
let hasTimedOut = false;
|
|
4200
4327
|
let timeoutHandle;
|
|
4201
4328
|
try {
|
|
@@ -4226,34 +4353,37 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4226
4353
|
this.logger.entity(name).info("Stopping component");
|
|
4227
4354
|
const isRunning = this.isComponentRunning(name);
|
|
4228
4355
|
const isStalled = stalledComponentNames.has(name);
|
|
4356
|
+
const currentState = this.componentStates.get(name);
|
|
4357
|
+
if (currentState === "stopped") {
|
|
4358
|
+
stoppedComponents.add(name);
|
|
4359
|
+
continue;
|
|
4360
|
+
}
|
|
4229
4361
|
const result2 = isRunning ? await this.stopComponentInternal(name) : shouldRetryStalled && isStalled ? await this.retryStalledComponent(name) : isStalled ? {
|
|
4230
4362
|
success: false,
|
|
4231
4363
|
componentName: name,
|
|
4232
|
-
reason:
|
|
4364
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_STALLED,
|
|
4233
4365
|
code: "component_stalled",
|
|
4234
4366
|
status: this.getComponentStatus(name)
|
|
4235
4367
|
} : {
|
|
4236
4368
|
success: false,
|
|
4237
4369
|
componentName: name,
|
|
4238
|
-
reason:
|
|
4370
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_RUNNING,
|
|
4239
4371
|
code: "component_not_running",
|
|
4240
4372
|
status: this.getComponentStatus(name)
|
|
4241
4373
|
};
|
|
4242
4374
|
if (result2.success) {
|
|
4243
|
-
stoppedComponents.
|
|
4375
|
+
stoppedComponents.add(name);
|
|
4244
4376
|
} else {
|
|
4245
4377
|
this.logger.entity(name).error(
|
|
4246
4378
|
"Component failed to stop, continuing with others: {{error.message}}",
|
|
4247
4379
|
{
|
|
4248
4380
|
params: {
|
|
4249
|
-
error: result2.error || new Error(
|
|
4381
|
+
error: result2.error || new Error(
|
|
4382
|
+
result2.reason || LIFECYCLE_MANAGER_MESSAGE_UNKNOWN_ERROR
|
|
4383
|
+
)
|
|
4250
4384
|
}
|
|
4251
4385
|
}
|
|
4252
4386
|
);
|
|
4253
|
-
const stallInfo = this.stalledComponents.get(name);
|
|
4254
|
-
if (stallInfo) {
|
|
4255
|
-
stalledComponents.push(stallInfo);
|
|
4256
|
-
}
|
|
4257
4387
|
if (shouldHaltOnStall) {
|
|
4258
4388
|
this.logger.warn(
|
|
4259
4389
|
"Halting shutdown after stall (haltOnStall=true)",
|
|
@@ -4269,21 +4399,32 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4269
4399
|
} else {
|
|
4270
4400
|
await shutdownOperation();
|
|
4271
4401
|
}
|
|
4402
|
+
const finalStalledNames = /* @__PURE__ */ new Set();
|
|
4403
|
+
for (const name of runningComponentsToStop) {
|
|
4404
|
+
if (this.stalledComponents.has(name)) {
|
|
4405
|
+
finalStalledNames.add(name);
|
|
4406
|
+
}
|
|
4407
|
+
}
|
|
4272
4408
|
if (!shouldRetryStalled) {
|
|
4273
4409
|
for (const name of stalledComponentNames) {
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
stalledComponents.push(stallInfo);
|
|
4410
|
+
if (this.stalledComponents.has(name)) {
|
|
4411
|
+
finalStalledNames.add(name);
|
|
4277
4412
|
}
|
|
4278
4413
|
}
|
|
4279
4414
|
}
|
|
4415
|
+
for (const name of runningComponentsToStop) {
|
|
4416
|
+
if (!finalStalledNames.has(name) && this.componentStates.get(name) === "stopped") {
|
|
4417
|
+
stoppedComponents.add(name);
|
|
4418
|
+
}
|
|
4419
|
+
}
|
|
4420
|
+
const stalledComponents = Array.from(finalStalledNames).map((name) => this.stalledComponents.get(name)).filter((stallInfo) => !!stallInfo);
|
|
4280
4421
|
const durationMS = Date.now() - startTime;
|
|
4281
4422
|
const isSuccess = !hasTimedOut && stalledComponents.length === 0;
|
|
4282
4423
|
this.logger[isSuccess ? "success" : "warn"](
|
|
4283
4424
|
isSuccess ? "Shutdown completed successfully" : "Shutdown attempt completed with stalled components or timeout",
|
|
4284
4425
|
{
|
|
4285
4426
|
params: {
|
|
4286
|
-
stopped: stoppedComponents.
|
|
4427
|
+
stopped: stoppedComponents.size,
|
|
4287
4428
|
stalled: stalledComponents.length,
|
|
4288
4429
|
durationMS
|
|
4289
4430
|
}
|
|
@@ -4291,7 +4432,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4291
4432
|
);
|
|
4292
4433
|
const result = {
|
|
4293
4434
|
success: isSuccess,
|
|
4294
|
-
stoppedComponents,
|
|
4435
|
+
stoppedComponents: Array.from(stoppedComponents),
|
|
4295
4436
|
stalledComponents,
|
|
4296
4437
|
durationMS,
|
|
4297
4438
|
timedOut: hasTimedOut || void 0,
|
|
@@ -4342,7 +4483,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4342
4483
|
return {
|
|
4343
4484
|
success: false,
|
|
4344
4485
|
componentName: name,
|
|
4345
|
-
reason:
|
|
4486
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_FOUND,
|
|
4346
4487
|
code: "component_not_found"
|
|
4347
4488
|
};
|
|
4348
4489
|
}
|
|
@@ -4353,12 +4494,15 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4353
4494
|
return {
|
|
4354
4495
|
success: false,
|
|
4355
4496
|
componentName: name,
|
|
4356
|
-
reason:
|
|
4497
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_RUNNING,
|
|
4357
4498
|
code: "component_not_running",
|
|
4358
4499
|
status: this.getComponentStatus(name)
|
|
4359
4500
|
};
|
|
4360
4501
|
}
|
|
4361
4502
|
this.logger.entity(name).warn("Retrying stalled component shutdown (force phase)");
|
|
4503
|
+
if (component.onShutdownForce) {
|
|
4504
|
+
this.issueStopAttemptToken(name);
|
|
4505
|
+
}
|
|
4362
4506
|
return this.shutdownComponentForce(name, component, {
|
|
4363
4507
|
gracefulPhaseRan: false,
|
|
4364
4508
|
gracefulTimedOut: false,
|
|
@@ -4378,7 +4522,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4378
4522
|
return {
|
|
4379
4523
|
success: false,
|
|
4380
4524
|
componentName: name,
|
|
4381
|
-
reason:
|
|
4525
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_SHUTDOWN_IN_PROGRESS,
|
|
4382
4526
|
code: "shutdown_in_progress"
|
|
4383
4527
|
};
|
|
4384
4528
|
}
|
|
@@ -4390,7 +4534,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4390
4534
|
return {
|
|
4391
4535
|
success: false,
|
|
4392
4536
|
componentName: name,
|
|
4393
|
-
reason:
|
|
4537
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_BULK_STARTUP_IN_PROGRESS,
|
|
4394
4538
|
code: "startup_in_progress"
|
|
4395
4539
|
};
|
|
4396
4540
|
}
|
|
@@ -4400,7 +4544,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4400
4544
|
return {
|
|
4401
4545
|
success: false,
|
|
4402
4546
|
componentName: name,
|
|
4403
|
-
reason:
|
|
4547
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_FOUND,
|
|
4404
4548
|
code: "component_not_found"
|
|
4405
4549
|
};
|
|
4406
4550
|
}
|
|
@@ -4409,7 +4553,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4409
4553
|
return {
|
|
4410
4554
|
success: false,
|
|
4411
4555
|
componentName: name,
|
|
4412
|
-
reason:
|
|
4556
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_STALLED,
|
|
4413
4557
|
code: "component_stalled",
|
|
4414
4558
|
status: this.getComponentStatus(name)
|
|
4415
4559
|
};
|
|
@@ -4473,6 +4617,9 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4473
4617
|
const timeoutMS = component.startupTimeoutMS;
|
|
4474
4618
|
const startAttemptToken = (0, import_ulid2.ulid)();
|
|
4475
4619
|
this.componentStartAttemptTokens.set(name, startAttemptToken);
|
|
4620
|
+
component._setUnexpectedStopHandler(
|
|
4621
|
+
(error) => this.handleComponentUnexpectedStop(name, startAttemptToken, error)
|
|
4622
|
+
);
|
|
4476
4623
|
const shutdownTokenAtStart = this.shutdownToken;
|
|
4477
4624
|
const didAutoAttachSignalsForComponentStartup = this.attachSignalsBeforeStartup ? this.autoAttachSignals("component startup") : false;
|
|
4478
4625
|
let timeoutHandle;
|
|
@@ -4515,6 +4662,18 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4515
4662
|
} else {
|
|
4516
4663
|
await startPromise;
|
|
4517
4664
|
}
|
|
4665
|
+
if (this.componentStartAttemptTokens.get(name) === startAttemptToken && this.componentStates.get(name) === "stopped" && !this.runningComponents.has(name)) {
|
|
4666
|
+
component._clearUnexpectedStopHandler();
|
|
4667
|
+
const error = this.componentErrors.get(name) ?? new Error(`Component "${name}" stopped unexpectedly during startup`);
|
|
4668
|
+
return {
|
|
4669
|
+
success: false,
|
|
4670
|
+
componentName: name,
|
|
4671
|
+
reason: error.message,
|
|
4672
|
+
code: "component_unexpected_stop",
|
|
4673
|
+
error,
|
|
4674
|
+
status: this.getComponentStatus(name)
|
|
4675
|
+
};
|
|
4676
|
+
}
|
|
4518
4677
|
if (this.isShuttingDown || shutdownTokenAtStart !== this.shutdownToken) {
|
|
4519
4678
|
this.componentStates.set(name, "running");
|
|
4520
4679
|
this.runningComponents.add(name);
|
|
@@ -4542,6 +4701,9 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4542
4701
|
this.componentStates.set(name, "running");
|
|
4543
4702
|
this.runningComponents.add(name);
|
|
4544
4703
|
this.stalledComponents.delete(name);
|
|
4704
|
+
if (shouldForceStalled) {
|
|
4705
|
+
this.issueStopAttemptToken(name);
|
|
4706
|
+
}
|
|
4545
4707
|
this.updateStartedFlag();
|
|
4546
4708
|
if (this.attachSignalsOnStart && this.runningComponents.size === 1) {
|
|
4547
4709
|
this.autoAttachSignals("first component start");
|
|
@@ -4561,9 +4723,24 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4561
4723
|
status: this.getComponentStatus(name)
|
|
4562
4724
|
};
|
|
4563
4725
|
} catch (error) {
|
|
4726
|
+
component._clearUnexpectedStopHandler();
|
|
4564
4727
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
4728
|
+
const isStartupTimeout = err instanceof ComponentStartTimeoutError && err.additionalInfo.componentName === name;
|
|
4729
|
+
const unexpectedStopError = this.componentErrors.get(name);
|
|
4730
|
+
if (this.componentStartAttemptTokens.get(name) === startAttemptToken && this.componentStates.get(name) === "stopped" && !this.runningComponents.has(name) && (isStartupTimeout || unexpectedStopError instanceof Error)) {
|
|
4731
|
+
return {
|
|
4732
|
+
success: false,
|
|
4733
|
+
componentName: name,
|
|
4734
|
+
reason: unexpectedStopError?.message || `Component "${name}" stopped unexpectedly during startup`,
|
|
4735
|
+
code: "component_unexpected_stop",
|
|
4736
|
+
error: unexpectedStopError || new Error(
|
|
4737
|
+
`Component "${name}" stopped unexpectedly during startup`
|
|
4738
|
+
),
|
|
4739
|
+
status: this.getComponentStatus(name)
|
|
4740
|
+
};
|
|
4741
|
+
}
|
|
4565
4742
|
this.componentErrors.set(name, err);
|
|
4566
|
-
if (
|
|
4743
|
+
if (isStartupTimeout) {
|
|
4567
4744
|
this.componentStates.set(name, "starting-timed-out");
|
|
4568
4745
|
this.logger.entity(name).error("Component startup timed out: {{error.message}}", {
|
|
4569
4746
|
params: { error: err }
|
|
@@ -4608,7 +4785,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4608
4785
|
return {
|
|
4609
4786
|
success: false,
|
|
4610
4787
|
componentName: name,
|
|
4611
|
-
reason:
|
|
4788
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_FOUND,
|
|
4612
4789
|
code: "component_not_found"
|
|
4613
4790
|
};
|
|
4614
4791
|
}
|
|
@@ -4616,7 +4793,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4616
4793
|
return {
|
|
4617
4794
|
success: false,
|
|
4618
4795
|
componentName: name,
|
|
4619
|
-
reason:
|
|
4796
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_STALLED,
|
|
4620
4797
|
code: "component_stalled",
|
|
4621
4798
|
status: this.getComponentStatus(name)
|
|
4622
4799
|
};
|
|
@@ -4625,7 +4802,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4625
4802
|
return {
|
|
4626
4803
|
success: false,
|
|
4627
4804
|
componentName: name,
|
|
4628
|
-
reason:
|
|
4805
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_RUNNING,
|
|
4629
4806
|
code: "component_not_running",
|
|
4630
4807
|
status: this.getComponentStatus(name)
|
|
4631
4808
|
};
|
|
@@ -4641,6 +4818,10 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4641
4818
|
};
|
|
4642
4819
|
}
|
|
4643
4820
|
if (options?.forceImmediate) {
|
|
4821
|
+
if (component.onShutdownForce) {
|
|
4822
|
+
this.issueStopAttemptToken(name);
|
|
4823
|
+
}
|
|
4824
|
+
component._clearUnexpectedStopHandler();
|
|
4644
4825
|
return this.shutdownComponentForce(name, component, {
|
|
4645
4826
|
gracefulPhaseRan: false,
|
|
4646
4827
|
gracefulTimedOut: false,
|
|
@@ -4777,9 +4958,11 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4777
4958
|
* Calls stop() with timeout
|
|
4778
4959
|
*/
|
|
4779
4960
|
async shutdownComponentGraceful(name, component, options) {
|
|
4961
|
+
component._clearUnexpectedStopHandler();
|
|
4780
4962
|
this.componentStates.set(name, "stopping");
|
|
4781
4963
|
this.logger.entity(name).info("Graceful shutdown started");
|
|
4782
4964
|
this.lifecycleEvents.componentStopping(name);
|
|
4965
|
+
const stopAttemptToken = this.issueStopAttemptToken(name);
|
|
4783
4966
|
const timeoutMS = options?.timeout ?? component.shutdownGracefulTimeoutMS;
|
|
4784
4967
|
let timeoutHandle;
|
|
4785
4968
|
try {
|
|
@@ -4800,7 +4983,16 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4800
4983
|
);
|
|
4801
4984
|
}
|
|
4802
4985
|
}
|
|
4803
|
-
Promise.resolve(stopPromise).
|
|
4986
|
+
Promise.resolve(stopPromise).then(
|
|
4987
|
+
() => this.handleLateStopResolution(
|
|
4988
|
+
name,
|
|
4989
|
+
stopAttemptToken,
|
|
4990
|
+
"graceful"
|
|
4991
|
+
),
|
|
4992
|
+
() => {
|
|
4993
|
+
}
|
|
4994
|
+
// Intentionally ignore errors after timeout
|
|
4995
|
+
).catch(() => {
|
|
4804
4996
|
});
|
|
4805
4997
|
reject(
|
|
4806
4998
|
new ComponentStopTimeoutError({
|
|
@@ -4819,9 +5011,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4819
5011
|
this.stalledComponents.delete(name);
|
|
4820
5012
|
this.updateStartedFlag();
|
|
4821
5013
|
if (this.detachSignalsOnStop && this.runningComponents.size === 0 && this.processSignalManager) {
|
|
4822
|
-
this.logger.info(
|
|
4823
|
-
"Auto-detaching process signals on last component stop"
|
|
4824
|
-
);
|
|
5014
|
+
this.logger.info(LIFECYCLE_MANAGER_LOG_AUTO_DETACH_LAST_COMPONENT_STOP);
|
|
4825
5015
|
this.detachSignals();
|
|
4826
5016
|
}
|
|
4827
5017
|
const timestamps = this.componentTimestamps.get(name) ?? {
|
|
@@ -4844,15 +5034,15 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4844
5034
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
4845
5035
|
this.componentErrors.set(name, err);
|
|
4846
5036
|
if (err instanceof ComponentStopTimeoutError && err.additionalInfo.componentName === name) {
|
|
4847
|
-
this.logger.entity(name).warn(
|
|
5037
|
+
this.logger.entity(name).warn(LIFECYCLE_MANAGER_MESSAGE_GRACEFUL_SHUTDOWN_TIMED_OUT);
|
|
4848
5038
|
this.lifecycleEvents.componentStopTimeout(name, err, {
|
|
4849
5039
|
timeoutMS,
|
|
4850
|
-
reason:
|
|
5040
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_GRACEFUL_SHUTDOWN_TIMED_OUT
|
|
4851
5041
|
});
|
|
4852
5042
|
return {
|
|
4853
5043
|
success: false,
|
|
4854
5044
|
componentName: name,
|
|
4855
|
-
reason:
|
|
5045
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_GRACEFUL_SHUTDOWN_TIMED_OUT,
|
|
4856
5046
|
code: "component_shutdown_timeout",
|
|
4857
5047
|
error: err,
|
|
4858
5048
|
status: this.getComponentStatus(name)
|
|
@@ -4895,8 +5085,6 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4895
5085
|
gracefulTimedOut: context.gracefulTimedOut
|
|
4896
5086
|
}
|
|
4897
5087
|
});
|
|
4898
|
-
const timeoutMS = component.shutdownForceTimeoutMS;
|
|
4899
|
-
let timeoutHandle;
|
|
4900
5088
|
if (!component.onShutdownForce) {
|
|
4901
5089
|
const stallInfo = {
|
|
4902
5090
|
name,
|
|
@@ -4930,6 +5118,9 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4930
5118
|
status: this.getComponentStatus(name)
|
|
4931
5119
|
};
|
|
4932
5120
|
}
|
|
5121
|
+
const timeoutMS = component.shutdownForceTimeoutMS;
|
|
5122
|
+
const { promise: stoppedDuringForcePromise, cleanup: cleanupForceWaiter } = this.createPendingForceStopWaiter(name);
|
|
5123
|
+
let timeoutHandle;
|
|
4933
5124
|
try {
|
|
4934
5125
|
const forcePromise = component.onShutdownForce();
|
|
4935
5126
|
if (timeoutMS > 0) {
|
|
@@ -4948,23 +5139,44 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4948
5139
|
);
|
|
4949
5140
|
}
|
|
4950
5141
|
}
|
|
4951
|
-
|
|
5142
|
+
const forceAttemptToken = this.componentStopAttemptTokens.get(name) ?? (0, import_ulid2.ulid)();
|
|
5143
|
+
Promise.resolve(forcePromise).then(
|
|
5144
|
+
() => this.handleLateStopResolution(
|
|
5145
|
+
name,
|
|
5146
|
+
forceAttemptToken,
|
|
5147
|
+
"force"
|
|
5148
|
+
),
|
|
5149
|
+
() => {
|
|
5150
|
+
}
|
|
5151
|
+
// Intentionally ignore errors after timeout
|
|
5152
|
+
).catch(() => {
|
|
4952
5153
|
});
|
|
4953
|
-
reject(
|
|
5154
|
+
reject(
|
|
5155
|
+
new Error(LIFECYCLE_MANAGER_MESSAGE_FORCE_SHUTDOWN_TIMED_OUT)
|
|
5156
|
+
);
|
|
4954
5157
|
}, timeoutMS);
|
|
4955
5158
|
});
|
|
4956
|
-
await Promise.race([
|
|
5159
|
+
await Promise.race([
|
|
5160
|
+
forcePromise,
|
|
5161
|
+
timeoutPromise,
|
|
5162
|
+
stoppedDuringForcePromise
|
|
5163
|
+
]);
|
|
4957
5164
|
} else {
|
|
4958
|
-
await forcePromise;
|
|
5165
|
+
await Promise.race([forcePromise, stoppedDuringForcePromise]);
|
|
5166
|
+
}
|
|
5167
|
+
if (this.componentStates.get(name) === "stopped" && !this.runningComponents.has(name)) {
|
|
5168
|
+
return {
|
|
5169
|
+
success: true,
|
|
5170
|
+
componentName: name,
|
|
5171
|
+
status: this.getComponentStatus(name)
|
|
5172
|
+
};
|
|
4959
5173
|
}
|
|
4960
5174
|
this.componentStates.set(name, "stopped");
|
|
4961
5175
|
this.runningComponents.delete(name);
|
|
4962
5176
|
this.stalledComponents.delete(name);
|
|
4963
5177
|
this.updateStartedFlag();
|
|
4964
5178
|
if (this.detachSignalsOnStop && this.runningComponents.size === 0 && this.processSignalManager) {
|
|
4965
|
-
this.logger.info(
|
|
4966
|
-
"Auto-detaching process signals on last component stop"
|
|
4967
|
-
);
|
|
5179
|
+
this.logger.info(LIFECYCLE_MANAGER_LOG_AUTO_DETACH_LAST_COMPONENT_STOP);
|
|
4968
5180
|
this.detachSignals();
|
|
4969
5181
|
}
|
|
4970
5182
|
const timestamps = this.componentTimestamps.get(name) ?? {
|
|
@@ -4985,8 +5197,15 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4985
5197
|
status: this.getComponentStatus(name)
|
|
4986
5198
|
};
|
|
4987
5199
|
} catch (error) {
|
|
5200
|
+
if (this.componentStates.get(name) === "stopped" && !this.runningComponents.has(name)) {
|
|
5201
|
+
return {
|
|
5202
|
+
success: true,
|
|
5203
|
+
componentName: name,
|
|
5204
|
+
status: this.getComponentStatus(name)
|
|
5205
|
+
};
|
|
5206
|
+
}
|
|
4988
5207
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
4989
|
-
const isTimeout = err.message ===
|
|
5208
|
+
const isTimeout = err.message === LIFECYCLE_MANAGER_MESSAGE_FORCE_SHUTDOWN_TIMED_OUT;
|
|
4990
5209
|
const stallInfo = {
|
|
4991
5210
|
name,
|
|
4992
5211
|
phase: "force",
|
|
@@ -5017,12 +5236,13 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
5017
5236
|
return {
|
|
5018
5237
|
success: false,
|
|
5019
5238
|
componentName: name,
|
|
5020
|
-
reason: isTimeout ?
|
|
5239
|
+
reason: isTimeout ? LIFECYCLE_MANAGER_MESSAGE_FORCE_SHUTDOWN_TIMED_OUT : err.message,
|
|
5021
5240
|
code: isTimeout ? "component_shutdown_timeout" : "unknown_error",
|
|
5022
5241
|
error: err,
|
|
5023
5242
|
status: this.getComponentStatus(name)
|
|
5024
5243
|
};
|
|
5025
5244
|
} finally {
|
|
5245
|
+
cleanupForceWaiter();
|
|
5026
5246
|
if (timeoutHandle) {
|
|
5027
5247
|
clearTimeout(timeoutHandle);
|
|
5028
5248
|
}
|
|
@@ -5099,7 +5319,9 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
5099
5319
|
"Failed to stop component during rollback, continuing: {{error.message}}",
|
|
5100
5320
|
{
|
|
5101
5321
|
params: {
|
|
5102
|
-
error: result.error || new Error(
|
|
5322
|
+
error: result.error || new Error(
|
|
5323
|
+
result.reason || LIFECYCLE_MANAGER_MESSAGE_UNKNOWN_ERROR
|
|
5324
|
+
)
|
|
5103
5325
|
}
|
|
5104
5326
|
}
|
|
5105
5327
|
);
|
|
@@ -5113,10 +5335,13 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
5113
5335
|
}
|
|
5114
5336
|
this.logger.info(`Auto-attaching process signals on ${trigger}`);
|
|
5115
5337
|
this.attachSignals();
|
|
5338
|
+
if (this.isStarting) {
|
|
5339
|
+
this.autoAttachedSignalsDuringStartup = true;
|
|
5340
|
+
}
|
|
5116
5341
|
return true;
|
|
5117
5342
|
}
|
|
5118
5343
|
autoDetachSignalsIfIdle(trigger) {
|
|
5119
|
-
if (!this.detachSignalsOnStop || this.runningComponents.size > 0 || !this.processSignalManager?.getStatus().isAttached) {
|
|
5344
|
+
if (!this.detachSignalsOnStop || this.isStarting || this.runningComponents.size > 0 || !this.processSignalManager?.getStatus().isAttached) {
|
|
5120
5345
|
return;
|
|
5121
5346
|
}
|
|
5122
5347
|
this.logger.info(`Auto-detaching process signals after ${trigger}`);
|
|
@@ -5158,6 +5383,210 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
5158
5383
|
}).catch(() => {
|
|
5159
5384
|
});
|
|
5160
5385
|
}
|
|
5386
|
+
consumeUnexpectedStopsDuringStartup(startedComponents, failedOptionalComponents) {
|
|
5387
|
+
if (this.unexpectedStopsDuringStartup.size === 0) {
|
|
5388
|
+
return { startedComponents: [...startedComponents] };
|
|
5389
|
+
}
|
|
5390
|
+
const remainingStartedComponents = [];
|
|
5391
|
+
let requiredFailure;
|
|
5392
|
+
for (const name of startedComponents) {
|
|
5393
|
+
const startupStopError = this.unexpectedStopsDuringStartup.get(name);
|
|
5394
|
+
if (startupStopError === void 0) {
|
|
5395
|
+
remainingStartedComponents.push(name);
|
|
5396
|
+
continue;
|
|
5397
|
+
}
|
|
5398
|
+
this.unexpectedStopsDuringStartup.delete(name);
|
|
5399
|
+
const error = startupStopError ?? new Error(`Component "${name}" stopped unexpectedly during startup`);
|
|
5400
|
+
const component = this.getComponent(name);
|
|
5401
|
+
if (component?.isOptional()) {
|
|
5402
|
+
if (!failedOptionalComponents.some((entry) => entry.name === name)) {
|
|
5403
|
+
failedOptionalComponents.push({ name, error });
|
|
5404
|
+
}
|
|
5405
|
+
this.logger.entity(name).warn(
|
|
5406
|
+
LIFECYCLE_MANAGER_LOG_OPTIONAL_COMPONENT_UNEXPECTED_STOP_DURING_STARTUP,
|
|
5407
|
+
{
|
|
5408
|
+
params: { error }
|
|
5409
|
+
}
|
|
5410
|
+
);
|
|
5411
|
+
continue;
|
|
5412
|
+
}
|
|
5413
|
+
this.logger.entity(name).error(
|
|
5414
|
+
LIFECYCLE_MANAGER_LOG_REQUIRED_COMPONENT_UNEXPECTED_STOP_DURING_STARTUP,
|
|
5415
|
+
{
|
|
5416
|
+
params: { error }
|
|
5417
|
+
}
|
|
5418
|
+
);
|
|
5419
|
+
requiredFailure ??= { name, error };
|
|
5420
|
+
}
|
|
5421
|
+
return {
|
|
5422
|
+
startedComponents: remainingStartedComponents,
|
|
5423
|
+
requiredFailure
|
|
5424
|
+
};
|
|
5425
|
+
}
|
|
5426
|
+
/**
|
|
5427
|
+
* Issues and returns a unique stop attempt token for a component.
|
|
5428
|
+
*
|
|
5429
|
+
* Each stop attempt (graceful or force-retry) gets a unique token.
|
|
5430
|
+
* The late-resolution handler captures this token in its closure so it can
|
|
5431
|
+
* skip any stall entries that were created by a *later* stop attempt — e.g. a
|
|
5432
|
+
* force-retry that also timed out after the original graceful promise floated
|
|
5433
|
+
* in the background.
|
|
5434
|
+
*/
|
|
5435
|
+
issueStopAttemptToken(name) {
|
|
5436
|
+
const next = (0, import_ulid2.ulid)();
|
|
5437
|
+
this.componentStopAttemptTokens.set(name, next);
|
|
5438
|
+
return next;
|
|
5439
|
+
}
|
|
5440
|
+
createPendingForceStopWaiter(name) {
|
|
5441
|
+
let isResolved = false;
|
|
5442
|
+
let waiters = this.pendingForceStopWaiters.get(name);
|
|
5443
|
+
if (!waiters) {
|
|
5444
|
+
waiters = /* @__PURE__ */ new Set();
|
|
5445
|
+
this.pendingForceStopWaiters.set(name, waiters);
|
|
5446
|
+
}
|
|
5447
|
+
let resolveWaiter;
|
|
5448
|
+
const promise = new Promise((resolve) => {
|
|
5449
|
+
resolveWaiter = () => {
|
|
5450
|
+
if (isResolved) {
|
|
5451
|
+
return;
|
|
5452
|
+
}
|
|
5453
|
+
isResolved = true;
|
|
5454
|
+
resolve();
|
|
5455
|
+
};
|
|
5456
|
+
});
|
|
5457
|
+
waiters.add(resolveWaiter);
|
|
5458
|
+
return {
|
|
5459
|
+
promise,
|
|
5460
|
+
cleanup: () => {
|
|
5461
|
+
const pending = this.pendingForceStopWaiters.get(name);
|
|
5462
|
+
if (!pending) {
|
|
5463
|
+
return;
|
|
5464
|
+
}
|
|
5465
|
+
pending.delete(resolveWaiter);
|
|
5466
|
+
if (pending.size === 0) {
|
|
5467
|
+
this.pendingForceStopWaiters.delete(name);
|
|
5468
|
+
}
|
|
5469
|
+
}
|
|
5470
|
+
};
|
|
5471
|
+
}
|
|
5472
|
+
resolvePendingForceStopWaiters(name) {
|
|
5473
|
+
const waiters = this.pendingForceStopWaiters.get(name);
|
|
5474
|
+
if (!waiters || waiters.size === 0) {
|
|
5475
|
+
return;
|
|
5476
|
+
}
|
|
5477
|
+
this.pendingForceStopWaiters.delete(name);
|
|
5478
|
+
for (const resolve of waiters) {
|
|
5479
|
+
resolve();
|
|
5480
|
+
}
|
|
5481
|
+
}
|
|
5482
|
+
/**
|
|
5483
|
+
* Called when a stop promise eventually resolves after its timeout path already fired.
|
|
5484
|
+
*
|
|
5485
|
+
* Usually this means a previously stalled component's original stop() or
|
|
5486
|
+
* onShutdownForce() promise finally resolved, so the manager can clear the
|
|
5487
|
+
* stall and transition the component to stopped without a manual retry.
|
|
5488
|
+
*
|
|
5489
|
+
* There is one extra overlap case for graceful stop(): stop() can resolve
|
|
5490
|
+
* after the graceful timeout but before onShutdownForce() itself times out.
|
|
5491
|
+
* In that window no stall entry exists yet, but the component still finished
|
|
5492
|
+
* stopping cleanly, so we finalize it here and let the later force-timeout
|
|
5493
|
+
* path observe the already-stopped state and no-op. This overlap fix is
|
|
5494
|
+
* scoped to the same stop token and will not cross a later retry attempt.
|
|
5495
|
+
*
|
|
5496
|
+
* Two guards prevent stale floating promises from incorrectly clearing state:
|
|
5497
|
+
*
|
|
5498
|
+
* 1. token guard — if a newer stop attempt (e.g. a retryStalled
|
|
5499
|
+
* force-retry) has started since this promise was launched, its token
|
|
5500
|
+
* won't match and we bail out immediately.
|
|
5501
|
+
*
|
|
5502
|
+
* 2. state/stall guard — if the component was unregistered, restarted, or
|
|
5503
|
+
* already cleared by another path, there will be neither a matching stall
|
|
5504
|
+
* entry nor the force-phase overlap state, so we bail out.
|
|
5505
|
+
*/
|
|
5506
|
+
handleLateStopResolution(name, token, source) {
|
|
5507
|
+
if (this.componentStopAttemptTokens.get(name) !== token) {
|
|
5508
|
+
return;
|
|
5509
|
+
}
|
|
5510
|
+
const currentState = this.componentStates.get(name);
|
|
5511
|
+
const stallInfo = this.stalledComponents.get(name);
|
|
5512
|
+
const isCompletedDuringForcePhase = source === "graceful" && !stallInfo && currentState === "force-stopping";
|
|
5513
|
+
if (stallInfo && currentState !== "stalled") {
|
|
5514
|
+
this.stalledComponents.delete(name);
|
|
5515
|
+
return;
|
|
5516
|
+
}
|
|
5517
|
+
if (!stallInfo && !isCompletedDuringForcePhase) {
|
|
5518
|
+
return;
|
|
5519
|
+
}
|
|
5520
|
+
const stalledDurationMS = stallInfo ? Date.now() - stallInfo.stalledAt : void 0;
|
|
5521
|
+
if (stallInfo) {
|
|
5522
|
+
this.stalledComponents.delete(name);
|
|
5523
|
+
}
|
|
5524
|
+
this.componentStates.set(name, "stopped");
|
|
5525
|
+
this.runningComponents.delete(name);
|
|
5526
|
+
this.componentErrors.set(name, null);
|
|
5527
|
+
this.updateStartedFlag();
|
|
5528
|
+
this.resolvePendingForceStopWaiters(name);
|
|
5529
|
+
if (this.detachSignalsOnStop && this.runningComponents.size === 0 && this.processSignalManager) {
|
|
5530
|
+
this.logger.info(LIFECYCLE_MANAGER_LOG_AUTO_DETACH_LAST_COMPONENT_STOP);
|
|
5531
|
+
this.detachSignals();
|
|
5532
|
+
}
|
|
5533
|
+
const timestamps = this.componentTimestamps.get(name) ?? {
|
|
5534
|
+
startedAt: null,
|
|
5535
|
+
stoppedAt: null
|
|
5536
|
+
};
|
|
5537
|
+
timestamps.stoppedAt = Date.now();
|
|
5538
|
+
this.componentTimestamps.set(name, timestamps);
|
|
5539
|
+
this.logger.entity(name).info(
|
|
5540
|
+
stallInfo ? "Stalled component completed stop late, stall cleared" : "Graceful stop completed after force phase started",
|
|
5541
|
+
stalledDurationMS ? { params: { stalledDurationMS } } : void 0
|
|
5542
|
+
);
|
|
5543
|
+
if (source === "force") {
|
|
5544
|
+
this.lifecycleEvents.componentShutdownForceCompleted(name);
|
|
5545
|
+
}
|
|
5546
|
+
if (stallInfo && stalledDurationMS !== void 0) {
|
|
5547
|
+
this.lifecycleEvents.componentStalledResolved(
|
|
5548
|
+
name,
|
|
5549
|
+
stallInfo,
|
|
5550
|
+
stalledDurationMS
|
|
5551
|
+
);
|
|
5552
|
+
}
|
|
5553
|
+
this.lifecycleEvents.componentStopped(name, this.getComponentStatus(name));
|
|
5554
|
+
}
|
|
5555
|
+
handleComponentUnexpectedStop(name, startAttemptToken, error) {
|
|
5556
|
+
const currentState = this.componentStates.get(name);
|
|
5557
|
+
if (
|
|
5558
|
+
// Startup-time self-stops are valid too: start() may still be awaiting
|
|
5559
|
+
// some async work while an internal listener has already observed that
|
|
5560
|
+
// the component died and reported it.
|
|
5561
|
+
currentState !== "starting" && currentState !== "running" || this.componentStartAttemptTokens.get(name) !== startAttemptToken
|
|
5562
|
+
) {
|
|
5563
|
+
return false;
|
|
5564
|
+
}
|
|
5565
|
+
this.runningComponents.delete(name);
|
|
5566
|
+
this.componentStates.set(name, "stopped");
|
|
5567
|
+
this.componentErrors.set(name, error ?? null);
|
|
5568
|
+
if (this.isStarting) {
|
|
5569
|
+
this.unexpectedStopsDuringStartup.set(name, error ?? null);
|
|
5570
|
+
}
|
|
5571
|
+
this.updateStartedFlag();
|
|
5572
|
+
if (this.detachSignalsOnStop && !this.isStarting && this.runningComponents.size === 0 && this.processSignalManager) {
|
|
5573
|
+
this.logger.info(LIFECYCLE_MANAGER_LOG_AUTO_DETACH_LAST_COMPONENT_STOP);
|
|
5574
|
+
this.detachSignals();
|
|
5575
|
+
}
|
|
5576
|
+
const timestamps = this.componentTimestamps.get(name) ?? {
|
|
5577
|
+
startedAt: null,
|
|
5578
|
+
stoppedAt: null
|
|
5579
|
+
};
|
|
5580
|
+
timestamps.stoppedAt = Date.now();
|
|
5581
|
+
this.componentTimestamps.set(name, timestamps);
|
|
5582
|
+
this.logger.entity(name).warn(
|
|
5583
|
+
error ? `Component stopped unexpectedly: ${error.message}` : "Component stopped unexpectedly",
|
|
5584
|
+
{ params: { error } }
|
|
5585
|
+
);
|
|
5586
|
+
this.lifecycleEvents.componentUnexpectedStop(name, error);
|
|
5587
|
+
this.lifecycleEvents.componentStopped(name, this.getComponentStatus(name));
|
|
5588
|
+
return true;
|
|
5589
|
+
}
|
|
5161
5590
|
/**
|
|
5162
5591
|
* Safe emit wrapper - prevents event handler errors from breaking lifecycle
|
|
5163
5592
|
*/
|
|
@@ -5729,105 +6158,87 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
5729
6158
|
}
|
|
5730
6159
|
}
|
|
5731
6160
|
/**
|
|
5732
|
-
*
|
|
6161
|
+
* Shared dispatch path for reload/info/debug requests. Logs the dispatch,
|
|
6162
|
+
* emits the signal event, then either invokes the user-supplied callback
|
|
6163
|
+
* (passing the broadcast function so the user controls when/whether to
|
|
6164
|
+
* broadcast) or broadcasts directly when no callback is configured.
|
|
5733
6165
|
*
|
|
5734
6166
|
* When called from signal handlers (source='signal'), the Promise is started
|
|
5735
|
-
* but not awaited
|
|
5736
|
-
* still notified and the work completes
|
|
5737
|
-
*
|
|
6167
|
+
* but not awaited — Node.js signal handlers cannot return values, so results
|
|
6168
|
+
* are not accessible. Components are still notified and the work completes.
|
|
5738
6169
|
* When called from manual triggers (source='trigger'), the Promise is awaited
|
|
5739
6170
|
* and results are returned for programmatic use.
|
|
5740
|
-
*
|
|
5741
|
-
* @param source - Whether triggered from signal manager or manual trigger
|
|
5742
6171
|
*/
|
|
5743
|
-
async
|
|
5744
|
-
this.logger.info(
|
|
5745
|
-
|
|
5746
|
-
if (
|
|
5747
|
-
const
|
|
5748
|
-
const result = this.onReloadRequested(broadcastFn);
|
|
6172
|
+
async handleSignalRequest(descriptor, source) {
|
|
6173
|
+
this.logger.info(descriptor.dispatchedLogLabel, { params: { source } });
|
|
6174
|
+
descriptor.emitSignal();
|
|
6175
|
+
if (descriptor.customCallback) {
|
|
6176
|
+
const result = descriptor.customCallback(descriptor.broadcast);
|
|
5749
6177
|
if (isPromise(result)) {
|
|
5750
6178
|
await result;
|
|
5751
6179
|
}
|
|
5752
6180
|
return {
|
|
5753
|
-
signal:
|
|
6181
|
+
signal: descriptor.signal,
|
|
5754
6182
|
results: [],
|
|
5755
6183
|
timedOut: false,
|
|
5756
6184
|
code: "ok"
|
|
5757
6185
|
};
|
|
5758
6186
|
}
|
|
5759
|
-
return
|
|
6187
|
+
return descriptor.broadcast();
|
|
6188
|
+
}
|
|
6189
|
+
async handleReloadRequest(source = "trigger") {
|
|
6190
|
+
return this.handleSignalRequest(
|
|
6191
|
+
{
|
|
6192
|
+
signal: "reload",
|
|
6193
|
+
dispatchedLogLabel: "Reload dispatched",
|
|
6194
|
+
emitSignal: () => this.lifecycleEvents.signalReload(),
|
|
6195
|
+
customCallback: this.onReloadRequested,
|
|
6196
|
+
broadcast: () => this.broadcastReload()
|
|
6197
|
+
},
|
|
6198
|
+
source
|
|
6199
|
+
);
|
|
5760
6200
|
}
|
|
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
6201
|
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 {
|
|
6202
|
+
return this.handleSignalRequest(
|
|
6203
|
+
{
|
|
5779
6204
|
signal: "info",
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
6205
|
+
dispatchedLogLabel: "Info dispatched",
|
|
6206
|
+
emitSignal: () => this.lifecycleEvents.signalInfo(),
|
|
6207
|
+
customCallback: this.onInfoRequested,
|
|
6208
|
+
broadcast: () => this.broadcastInfo()
|
|
6209
|
+
},
|
|
6210
|
+
source
|
|
6211
|
+
);
|
|
5786
6212
|
}
|
|
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
6213
|
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 {
|
|
6214
|
+
return this.handleSignalRequest(
|
|
6215
|
+
{
|
|
5805
6216
|
signal: "debug",
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
6217
|
+
dispatchedLogLabel: "Debug dispatched",
|
|
6218
|
+
emitSignal: () => this.lifecycleEvents.signalDebug(),
|
|
6219
|
+
customCallback: this.onDebugRequested,
|
|
6220
|
+
broadcast: () => this.broadcastDebug()
|
|
6221
|
+
},
|
|
6222
|
+
source
|
|
6223
|
+
);
|
|
5812
6224
|
}
|
|
5813
6225
|
/**
|
|
5814
|
-
*
|
|
5815
|
-
*
|
|
5816
|
-
*
|
|
6226
|
+
* Shared signal broadcast pipeline used by reload/info/debug.
|
|
6227
|
+
* Iterates running components, runs the picked handler with timeout, and
|
|
6228
|
+
* aggregates per-component results into a SignalBroadcastResult.
|
|
5817
6229
|
*/
|
|
5818
|
-
async
|
|
6230
|
+
async runSignalBroadcast(descriptor) {
|
|
5819
6231
|
const results = [];
|
|
5820
|
-
const
|
|
6232
|
+
const targets = this.components.filter(
|
|
5821
6233
|
(component) => this.runningComponents.has(component.getName())
|
|
5822
6234
|
);
|
|
5823
6235
|
if (this.isStarting) {
|
|
5824
|
-
this.logger.info(
|
|
5825
|
-
"Reload during startup: only reloading already-started components"
|
|
5826
|
-
);
|
|
6236
|
+
this.logger.info(descriptor.startupLog);
|
|
5827
6237
|
}
|
|
5828
|
-
for (const component of
|
|
6238
|
+
for (const component of targets) {
|
|
5829
6239
|
const name = component.getName();
|
|
5830
|
-
|
|
6240
|
+
const handler = descriptor.pickHandler(component);
|
|
6241
|
+
if (!handler) {
|
|
5831
6242
|
results.push({
|
|
5832
6243
|
name,
|
|
5833
6244
|
called: false,
|
|
@@ -5837,13 +6248,13 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
5837
6248
|
});
|
|
5838
6249
|
continue;
|
|
5839
6250
|
}
|
|
5840
|
-
|
|
6251
|
+
descriptor.emitStarted(name);
|
|
5841
6252
|
const timeoutMS = component.signalTimeoutMS;
|
|
5842
6253
|
let timeoutHandle;
|
|
5843
6254
|
const timeoutResult = { timedOut: true };
|
|
5844
6255
|
try {
|
|
5845
|
-
const
|
|
5846
|
-
const handlerPromise = isPromise(
|
|
6256
|
+
const handlerResult = handler();
|
|
6257
|
+
const handlerPromise = isPromise(handlerResult) ? handlerResult : Promise.resolve(handlerResult);
|
|
5847
6258
|
const outcome = timeoutMS > 0 ? await Promise.race([
|
|
5848
6259
|
handlerPromise,
|
|
5849
6260
|
new Promise((resolve) => {
|
|
@@ -5853,7 +6264,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
5853
6264
|
})
|
|
5854
6265
|
]) : await handlerPromise;
|
|
5855
6266
|
if (outcome === timeoutResult) {
|
|
5856
|
-
this.logger.entity(name).warn(
|
|
6267
|
+
this.logger.entity(name).warn(descriptor.timeoutLog, {
|
|
5857
6268
|
params: { timeoutMS }
|
|
5858
6269
|
});
|
|
5859
6270
|
Promise.resolve(handlerPromise).catch(() => {
|
|
@@ -5866,7 +6277,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
5866
6277
|
code: "timeout"
|
|
5867
6278
|
});
|
|
5868
6279
|
} else {
|
|
5869
|
-
|
|
6280
|
+
descriptor.emitCompleted(name);
|
|
5870
6281
|
results.push({
|
|
5871
6282
|
name,
|
|
5872
6283
|
called: true,
|
|
@@ -5877,10 +6288,10 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
5877
6288
|
}
|
|
5878
6289
|
} catch (error) {
|
|
5879
6290
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
5880
|
-
this.logger.entity(name).error(
|
|
6291
|
+
this.logger.entity(name).error(descriptor.errorLog, {
|
|
5881
6292
|
params: { error: err }
|
|
5882
6293
|
});
|
|
5883
|
-
|
|
6294
|
+
descriptor.emitFailed(name, err);
|
|
5884
6295
|
results.push({
|
|
5885
6296
|
name,
|
|
5886
6297
|
called: true,
|
|
@@ -5901,108 +6312,45 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
5901
6312
|
const isAllTimeout = calledResults.length > 0 && calledResults.every((result) => result.timedOut);
|
|
5902
6313
|
const code = hasError ? isAllError ? "error" : "partial_error" : hasTimeout ? isAllTimeout ? "timeout" : "partial_timeout" : "ok";
|
|
5903
6314
|
return {
|
|
5904
|
-
signal:
|
|
6315
|
+
signal: descriptor.signal,
|
|
5905
6316
|
results,
|
|
5906
6317
|
timedOut: hasTimeout,
|
|
5907
6318
|
code
|
|
5908
6319
|
};
|
|
5909
6320
|
}
|
|
6321
|
+
/**
|
|
6322
|
+
* Broadcast reload signal to all running components.
|
|
6323
|
+
* Calls onReload() on components that implement it.
|
|
6324
|
+
* Continues on errors - collects all results.
|
|
6325
|
+
*/
|
|
6326
|
+
async broadcastReload() {
|
|
6327
|
+
return this.runSignalBroadcast({
|
|
6328
|
+
signal: "reload",
|
|
6329
|
+
pickHandler: (component) => component.onReload?.bind(component),
|
|
6330
|
+
startupLog: "Reload during startup: only reloading already-started components",
|
|
6331
|
+
timeoutLog: "Reload handler timed out",
|
|
6332
|
+
errorLog: "Reload failed: {{error.message}}",
|
|
6333
|
+
emitStarted: (name) => this.lifecycleEvents.componentReloadStarted(name),
|
|
6334
|
+
emitCompleted: (name) => this.lifecycleEvents.componentReloadCompleted(name),
|
|
6335
|
+
emitFailed: (name, error) => this.lifecycleEvents.componentReloadFailed(name, error)
|
|
6336
|
+
});
|
|
6337
|
+
}
|
|
5910
6338
|
/**
|
|
5911
6339
|
* Broadcast info signal to all running components.
|
|
5912
6340
|
* Calls onInfo() on components that implement it.
|
|
5913
6341
|
* Continues on errors - collects all results.
|
|
5914
6342
|
*/
|
|
5915
6343
|
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 {
|
|
6344
|
+
return this.runSignalBroadcast({
|
|
6001
6345
|
signal: "info",
|
|
6002
|
-
|
|
6003
|
-
|
|
6004
|
-
|
|
6005
|
-
|
|
6346
|
+
pickHandler: (component) => component.onInfo?.bind(component),
|
|
6347
|
+
startupLog: "Info during startup: only notifying already-started components",
|
|
6348
|
+
timeoutLog: "Info handler timed out",
|
|
6349
|
+
errorLog: "Info handler failed: {{error.message}}",
|
|
6350
|
+
emitStarted: (name) => this.lifecycleEvents.componentInfoStarted(name),
|
|
6351
|
+
emitCompleted: (name) => this.lifecycleEvents.componentInfoCompleted(name),
|
|
6352
|
+
emitFailed: (name, error) => this.lifecycleEvents.componentInfoFailed(name, error)
|
|
6353
|
+
});
|
|
6006
6354
|
}
|
|
6007
6355
|
/**
|
|
6008
6356
|
* Broadcast debug signal to all running components.
|
|
@@ -6010,96 +6358,16 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
6010
6358
|
* Continues on errors - collects all results.
|
|
6011
6359
|
*/
|
|
6012
6360
|
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 {
|
|
6361
|
+
return this.runSignalBroadcast({
|
|
6098
6362
|
signal: "debug",
|
|
6099
|
-
|
|
6100
|
-
|
|
6101
|
-
|
|
6102
|
-
|
|
6363
|
+
pickHandler: (component) => component.onDebug?.bind(component),
|
|
6364
|
+
startupLog: "Debug during startup: only notifying already-started components",
|
|
6365
|
+
timeoutLog: "Debug handler timed out",
|
|
6366
|
+
errorLog: "Debug handler failed: {{error.message}}",
|
|
6367
|
+
emitStarted: (name) => this.lifecycleEvents.componentDebugStarted(name),
|
|
6368
|
+
emitCompleted: (name) => this.lifecycleEvents.componentDebugCompleted(name),
|
|
6369
|
+
emitFailed: (name, error) => this.lifecycleEvents.componentDebugFailed(name, error)
|
|
6370
|
+
});
|
|
6103
6371
|
}
|
|
6104
6372
|
};
|
|
6105
6373
|
|
|
@@ -6125,6 +6393,12 @@ var BaseComponent = class {
|
|
|
6125
6393
|
name;
|
|
6126
6394
|
/** Reference to component-scoped lifecycle (set by manager when registered) */
|
|
6127
6395
|
lifecycle;
|
|
6396
|
+
/** @internal Set by LifecycleManager while the component is running. */
|
|
6397
|
+
_unexpectedStopHandler;
|
|
6398
|
+
/** @internal Incremented whenever the unexpected-stop handler is re-armed or cleared. */
|
|
6399
|
+
_unexpectedStopGeneration = 0;
|
|
6400
|
+
/** @internal Flag indicating whether this component is currently registered with a LifecycleManager */
|
|
6401
|
+
_isRegistered = false;
|
|
6128
6402
|
/**
|
|
6129
6403
|
* Create a new component
|
|
6130
6404
|
*
|
|
@@ -6159,6 +6433,37 @@ var BaseComponent = class {
|
|
|
6159
6433
|
// Default if undefined/null/non-finite
|
|
6160
6434
|
);
|
|
6161
6435
|
}
|
|
6436
|
+
/** @internal Called by LifecycleManager after a successful start. */
|
|
6437
|
+
_setUnexpectedStopHandler(handler) {
|
|
6438
|
+
this._unexpectedStopGeneration += 1;
|
|
6439
|
+
this._unexpectedStopHandler = handler;
|
|
6440
|
+
const generation = this._unexpectedStopGeneration;
|
|
6441
|
+
this.reportUnexpectedStop = (error) => {
|
|
6442
|
+
if (this._unexpectedStopGeneration !== generation) {
|
|
6443
|
+
return false;
|
|
6444
|
+
}
|
|
6445
|
+
return this._unexpectedStopHandler?.(error) ?? false;
|
|
6446
|
+
};
|
|
6447
|
+
}
|
|
6448
|
+
/** @internal Called by LifecycleManager when stop begins or component is unregistered. */
|
|
6449
|
+
_clearUnexpectedStopHandler() {
|
|
6450
|
+
this._unexpectedStopGeneration += 1;
|
|
6451
|
+
this._unexpectedStopHandler = void 0;
|
|
6452
|
+
this.reportUnexpectedStop = () => false;
|
|
6453
|
+
}
|
|
6454
|
+
/** @internal Called by LifecycleManager when registering the component. */
|
|
6455
|
+
_markRegistered() {
|
|
6456
|
+
this._isRegistered = true;
|
|
6457
|
+
}
|
|
6458
|
+
/** @internal Called by LifecycleManager when unregistering the component. */
|
|
6459
|
+
_markUnregistered() {
|
|
6460
|
+
this._isRegistered = false;
|
|
6461
|
+
this.lifecycle = void 0;
|
|
6462
|
+
}
|
|
6463
|
+
/** @internal Check if the component is registered with any LifecycleManager. */
|
|
6464
|
+
_isRegisteredWithManager() {
|
|
6465
|
+
return this._isRegistered;
|
|
6466
|
+
}
|
|
6162
6467
|
/**
|
|
6163
6468
|
* Get component name
|
|
6164
6469
|
*/
|
|
@@ -6177,6 +6482,38 @@ var BaseComponent = class {
|
|
|
6177
6482
|
isOptional() {
|
|
6178
6483
|
return this.optional;
|
|
6179
6484
|
}
|
|
6485
|
+
/**
|
|
6486
|
+
* Run-scoped unexpected-stop callback. Rebound by LifecycleManager on each
|
|
6487
|
+
* successful start so captured references from older runs go stale.
|
|
6488
|
+
*/
|
|
6489
|
+
reportUnexpectedStop = () => false;
|
|
6490
|
+
/**
|
|
6491
|
+
* Get this component's own status from the manager's perspective.
|
|
6492
|
+
*
|
|
6493
|
+
* Equivalent to `this.lifecycle.getComponentStatus(this.getName())` but without
|
|
6494
|
+
* needing to pass the name. Returns `undefined` if the component is not registered.
|
|
6495
|
+
*
|
|
6496
|
+
* Check `status?.state === 'running'` to test whether the component is currently running.
|
|
6497
|
+
*/
|
|
6498
|
+
getSelfStatus() {
|
|
6499
|
+
return this.lifecycle?.getComponentStatus(this.name);
|
|
6500
|
+
}
|
|
6501
|
+
/**
|
|
6502
|
+
* Capture a run-scoped unexpected-stop reporter for async listeners created during start().
|
|
6503
|
+
*
|
|
6504
|
+
* Unlike calling `this.reportUnexpectedStop()` later, the returned callback becomes a no-op
|
|
6505
|
+
* once the component is stopped, unregistered, or restarted. This prevents stale listeners
|
|
6506
|
+
* from a previous run from stopping a newer run of the same component instance.
|
|
6507
|
+
*/
|
|
6508
|
+
getUnexpectedStopReporter() {
|
|
6509
|
+
const generation = this._unexpectedStopGeneration;
|
|
6510
|
+
return (error) => {
|
|
6511
|
+
if (this._unexpectedStopGeneration !== generation) {
|
|
6512
|
+
return false;
|
|
6513
|
+
}
|
|
6514
|
+
return this._unexpectedStopHandler?.(error) ?? false;
|
|
6515
|
+
};
|
|
6516
|
+
}
|
|
6180
6517
|
};
|
|
6181
6518
|
// Annotate the CommonJS export names for ESM import in node:
|
|
6182
6519
|
0 && (module.exports = {
|