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
|
@@ -1213,6 +1213,16 @@ var LifecycleManagerEvents = class {
|
|
|
1213
1213
|
code: info?.code
|
|
1214
1214
|
});
|
|
1215
1215
|
}
|
|
1216
|
+
componentStalledResolved(name, stallInfo, stalledDurationMS) {
|
|
1217
|
+
this.emit("component:stalled-resolved", {
|
|
1218
|
+
name,
|
|
1219
|
+
stallInfo,
|
|
1220
|
+
stalledDurationMS
|
|
1221
|
+
});
|
|
1222
|
+
}
|
|
1223
|
+
componentUnexpectedStop(name, error) {
|
|
1224
|
+
this.emit("component:unexpected-stop", { name, error });
|
|
1225
|
+
}
|
|
1216
1226
|
componentShutdownForceCompleted(name) {
|
|
1217
1227
|
this.emit("component:shutdown-force-completed", { name });
|
|
1218
1228
|
}
|
|
@@ -1399,6 +1409,26 @@ var lifecycleManagerErrCodes = {
|
|
|
1399
1409
|
StopTimeout: "StopTimeout"
|
|
1400
1410
|
};
|
|
1401
1411
|
|
|
1412
|
+
// src/lib/lifecycle-manager/constants.ts
|
|
1413
|
+
var LIFECYCLE_MANAGER_MESSAGE_BULK_OPERATION_IN_PROGRESS = "Cannot unregister during bulk operation";
|
|
1414
|
+
var LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_FOUND = "Component not found";
|
|
1415
|
+
var LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_RUNNING = "Component not running";
|
|
1416
|
+
var LIFECYCLE_MANAGER_MESSAGE_COMPONENT_STALLED = "Component is stalled";
|
|
1417
|
+
var LIFECYCLE_MANAGER_MESSAGE_BULK_STARTUP_IN_PROGRESS = "Bulk startup in progress";
|
|
1418
|
+
var LIFECYCLE_MANAGER_MESSAGE_SHUTDOWN_IN_PROGRESS = "Shutdown in progress";
|
|
1419
|
+
var LIFECYCLE_MANAGER_MESSAGE_UNKNOWN_ERROR = "Unknown error";
|
|
1420
|
+
var LIFECYCLE_MANAGER_LOG_AUTO_DETACH_LAST_COMPONENT_STOP = "Auto-detaching process signals on last component stop";
|
|
1421
|
+
var LIFECYCLE_MANAGER_LOG_LOGGER_EXIT_DURING_SHUTDOWN = "Logger exit called during shutdown, waiting...";
|
|
1422
|
+
var LIFECYCLE_MANAGER_LOG_MESSAGE_HANDLER_FAILED = "Message handler failed: {{error.message}}";
|
|
1423
|
+
var LIFECYCLE_MANAGER_MESSAGE_GRACEFUL_SHUTDOWN_TIMED_OUT = "Graceful shutdown timed out";
|
|
1424
|
+
var LIFECYCLE_MANAGER_MESSAGE_FORCE_SHUTDOWN_TIMED_OUT = "Force shutdown timed out";
|
|
1425
|
+
var LIFECYCLE_MANAGER_LOG_OPTIONAL_COMPONENT_UNEXPECTED_STOP_DURING_STARTUP = "Optional component stopped unexpectedly during startup, continuing: {{error.message}}";
|
|
1426
|
+
var LIFECYCLE_MANAGER_LOG_REQUIRED_COMPONENT_UNEXPECTED_STOP_DURING_STARTUP = "Required component stopped unexpectedly during startup: {{error.message}}";
|
|
1427
|
+
var LIFECYCLE_MANAGER_MESSAGE_REGISTER_SHUTDOWN_IN_PROGRESS = "Cannot register component while shutdown is in progress (isShuttingDown=true).";
|
|
1428
|
+
var LIFECYCLE_MANAGER_MESSAGE_REGISTER_REQUIRED_DEPENDENCY_DURING_STARTUP = "Cannot register component during startup when it is a required dependency for other components.";
|
|
1429
|
+
var LIFECYCLE_MANAGER_MESSAGE_DUPLICATE_COMPONENT_INSTANCE = "Component instance is already registered.";
|
|
1430
|
+
var LIFECYCLE_MANAGER_MESSAGE_DUPLICATE_COMPONENT_INSTANCE_EXTERNAL = "Component instance is already registered with another lifecycle manager.";
|
|
1431
|
+
|
|
1402
1432
|
// src/lib/process-signal-manager.ts
|
|
1403
1433
|
import { ulid } from "ulid";
|
|
1404
1434
|
import readline from "readline";
|
|
@@ -1943,8 +1973,15 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
1943
1973
|
componentTimestamps = /* @__PURE__ */ new Map();
|
|
1944
1974
|
componentErrors = /* @__PURE__ */ new Map();
|
|
1945
1975
|
componentStartAttemptTokens = /* @__PURE__ */ new Map();
|
|
1976
|
+
// Use per-stop ULIDs instead of incrementing counters because a stalled
|
|
1977
|
+
// component can be unregistered and replaced by a same-name instance before
|
|
1978
|
+
// the old floating stop promise settles.
|
|
1979
|
+
componentStopAttemptTokens = /* @__PURE__ */ new Map();
|
|
1980
|
+
pendingForceStopWaiters = /* @__PURE__ */ new Map();
|
|
1981
|
+
unexpectedStopsDuringStartup = /* @__PURE__ */ new Map();
|
|
1946
1982
|
// State flags
|
|
1947
1983
|
isStarting = false;
|
|
1984
|
+
autoAttachedSignalsDuringStartup = false;
|
|
1948
1985
|
isStarted = false;
|
|
1949
1986
|
isShuttingDown = false;
|
|
1950
1987
|
// Unique token used to detect shutdowns that happened during async start().
|
|
@@ -2093,7 +2130,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2093
2130
|
*/
|
|
2094
2131
|
async unregisterComponent(name, options) {
|
|
2095
2132
|
if (this.isStarting || this.isShuttingDown) {
|
|
2096
|
-
this.logger.entity(name).warn(
|
|
2133
|
+
this.logger.entity(name).warn(LIFECYCLE_MANAGER_MESSAGE_BULK_OPERATION_IN_PROGRESS, {
|
|
2097
2134
|
params: {
|
|
2098
2135
|
isStarting: this.isStarting,
|
|
2099
2136
|
isShuttingDown: this.isShuttingDown
|
|
@@ -2102,7 +2139,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2102
2139
|
return {
|
|
2103
2140
|
success: false,
|
|
2104
2141
|
componentName: name,
|
|
2105
|
-
reason:
|
|
2142
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_BULK_OPERATION_IN_PROGRESS,
|
|
2106
2143
|
code: "bulk_operation_in_progress",
|
|
2107
2144
|
wasStopped: false,
|
|
2108
2145
|
wasRegistered: this.hasComponent(name)
|
|
@@ -2110,11 +2147,11 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2110
2147
|
}
|
|
2111
2148
|
const component = this.getComponent(name);
|
|
2112
2149
|
if (!component) {
|
|
2113
|
-
this.logger.entity(name).warn(
|
|
2150
|
+
this.logger.entity(name).warn(LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_FOUND);
|
|
2114
2151
|
return {
|
|
2115
2152
|
success: false,
|
|
2116
2153
|
componentName: name,
|
|
2117
|
-
reason:
|
|
2154
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_FOUND,
|
|
2118
2155
|
code: "component_not_found",
|
|
2119
2156
|
wasStopped: false,
|
|
2120
2157
|
wasRegistered: false
|
|
@@ -2127,7 +2164,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2127
2164
|
return {
|
|
2128
2165
|
success: false,
|
|
2129
2166
|
componentName: name,
|
|
2130
|
-
reason:
|
|
2167
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_STALLED,
|
|
2131
2168
|
code: "stop_failed",
|
|
2132
2169
|
stopFailureReason: "stalled",
|
|
2133
2170
|
wasStopped: false,
|
|
@@ -2179,10 +2216,14 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2179
2216
|
wasStopped = true;
|
|
2180
2217
|
}
|
|
2181
2218
|
this.components = this.components.filter((c) => c.getName() !== name);
|
|
2219
|
+
component._clearUnexpectedStopHandler();
|
|
2220
|
+
component._markUnregistered();
|
|
2182
2221
|
this.componentStates.delete(name);
|
|
2183
2222
|
this.componentTimestamps.delete(name);
|
|
2184
2223
|
this.componentErrors.delete(name);
|
|
2185
2224
|
this.componentStartAttemptTokens.delete(name);
|
|
2225
|
+
this.componentStopAttemptTokens.delete(name);
|
|
2226
|
+
this.pendingForceStopWaiters.delete(name);
|
|
2186
2227
|
this.stalledComponents.delete(name);
|
|
2187
2228
|
this.runningComponents.delete(name);
|
|
2188
2229
|
this.updateStartedFlag();
|
|
@@ -2529,7 +2570,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2529
2570
|
startedComponents: [],
|
|
2530
2571
|
failedOptionalComponents: [],
|
|
2531
2572
|
skippedDueToDependency: [],
|
|
2532
|
-
reason:
|
|
2573
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_SHUTDOWN_IN_PROGRESS,
|
|
2533
2574
|
code: "shutdown_in_progress",
|
|
2534
2575
|
durationMS: Date.now() - startTime
|
|
2535
2576
|
};
|
|
@@ -2589,6 +2630,8 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2589
2630
|
};
|
|
2590
2631
|
}
|
|
2591
2632
|
this.isStarting = true;
|
|
2633
|
+
this.autoAttachedSignalsDuringStartup = false;
|
|
2634
|
+
this.unexpectedStopsDuringStartup.clear();
|
|
2592
2635
|
this.resetRepeatedShutdownRequestState();
|
|
2593
2636
|
this.shutdownMethod = null;
|
|
2594
2637
|
this.lastShutdownResult = null;
|
|
@@ -2721,13 +2764,49 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2721
2764
|
error: result.error,
|
|
2722
2765
|
durationMS: Date.now() - startTime
|
|
2723
2766
|
};
|
|
2767
|
+
} else if (result.code === "component_unexpected_stop") {
|
|
2768
|
+
this.unexpectedStopsDuringStartup.delete(name);
|
|
2769
|
+
const error = result.error || new Error(
|
|
2770
|
+
result.reason || `Component "${name}" stopped unexpectedly`
|
|
2771
|
+
);
|
|
2772
|
+
if (component.isOptional()) {
|
|
2773
|
+
if (!failedOptionalComponents.some((entry) => entry.name === name)) {
|
|
2774
|
+
failedOptionalComponents.push({ name, error });
|
|
2775
|
+
}
|
|
2776
|
+
this.logger.entity(name).warn(
|
|
2777
|
+
LIFECYCLE_MANAGER_LOG_OPTIONAL_COMPONENT_UNEXPECTED_STOP_DURING_STARTUP,
|
|
2778
|
+
{
|
|
2779
|
+
params: { error }
|
|
2780
|
+
}
|
|
2781
|
+
);
|
|
2782
|
+
} else {
|
|
2783
|
+
this.logger.entity(name).error(
|
|
2784
|
+
LIFECYCLE_MANAGER_LOG_REQUIRED_COMPONENT_UNEXPECTED_STOP_DURING_STARTUP,
|
|
2785
|
+
{
|
|
2786
|
+
params: { error }
|
|
2787
|
+
}
|
|
2788
|
+
);
|
|
2789
|
+
await this.rollbackStartup(startedComponents);
|
|
2790
|
+
return {
|
|
2791
|
+
success: false,
|
|
2792
|
+
startedComponents: [],
|
|
2793
|
+
failedOptionalComponents,
|
|
2794
|
+
skippedDueToDependency: Array.from(skippedDueToDependency),
|
|
2795
|
+
reason: error.message,
|
|
2796
|
+
code: "component_unexpected_stop",
|
|
2797
|
+
error,
|
|
2798
|
+
durationMS: Date.now() - startTime
|
|
2799
|
+
};
|
|
2800
|
+
}
|
|
2724
2801
|
} else {
|
|
2725
2802
|
if (component.isOptional()) {
|
|
2726
2803
|
this.logger.entity(name).warn(
|
|
2727
2804
|
"Optional component failed to start, continuing: {{error.message}}",
|
|
2728
2805
|
{
|
|
2729
2806
|
params: {
|
|
2730
|
-
error: result.error || new Error(
|
|
2807
|
+
error: result.error || new Error(
|
|
2808
|
+
result.reason || LIFECYCLE_MANAGER_MESSAGE_UNKNOWN_ERROR
|
|
2809
|
+
)
|
|
2731
2810
|
}
|
|
2732
2811
|
}
|
|
2733
2812
|
);
|
|
@@ -2741,14 +2820,18 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2741
2820
|
}
|
|
2742
2821
|
failedOptionalComponents.push({
|
|
2743
2822
|
name,
|
|
2744
|
-
error: result.error || new Error(
|
|
2823
|
+
error: result.error || new Error(
|
|
2824
|
+
result.reason || LIFECYCLE_MANAGER_MESSAGE_UNKNOWN_ERROR
|
|
2825
|
+
)
|
|
2745
2826
|
});
|
|
2746
2827
|
} else {
|
|
2747
2828
|
this.logger.entity(name).error(
|
|
2748
2829
|
"Required component failed to start, rolling back: {{error.message}}",
|
|
2749
2830
|
{
|
|
2750
2831
|
params: {
|
|
2751
|
-
error: result.error || new Error(
|
|
2832
|
+
error: result.error || new Error(
|
|
2833
|
+
result.reason || LIFECYCLE_MANAGER_MESSAGE_UNKNOWN_ERROR
|
|
2834
|
+
)
|
|
2752
2835
|
}
|
|
2753
2836
|
}
|
|
2754
2837
|
);
|
|
@@ -2765,6 +2848,25 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2765
2848
|
};
|
|
2766
2849
|
}
|
|
2767
2850
|
}
|
|
2851
|
+
const unexpectedStopResult2 = this.consumeUnexpectedStopsDuringStartup(
|
|
2852
|
+
startedComponents,
|
|
2853
|
+
failedOptionalComponents
|
|
2854
|
+
);
|
|
2855
|
+
startedComponents.splice(0, startedComponents.length);
|
|
2856
|
+
startedComponents.push(...unexpectedStopResult2.startedComponents);
|
|
2857
|
+
if (unexpectedStopResult2.requiredFailure) {
|
|
2858
|
+
await this.rollbackStartup(startedComponents);
|
|
2859
|
+
return {
|
|
2860
|
+
success: false,
|
|
2861
|
+
startedComponents: [],
|
|
2862
|
+
failedOptionalComponents,
|
|
2863
|
+
skippedDueToDependency: Array.from(skippedDueToDependency),
|
|
2864
|
+
reason: unexpectedStopResult2.requiredFailure.error.message,
|
|
2865
|
+
code: "component_unexpected_stop",
|
|
2866
|
+
error: unexpectedStopResult2.requiredFailure.error,
|
|
2867
|
+
durationMS: Date.now() - startTime
|
|
2868
|
+
};
|
|
2869
|
+
}
|
|
2768
2870
|
}
|
|
2769
2871
|
if (hasTimedOut) {
|
|
2770
2872
|
const durationMS2 = Date.now() - startTime;
|
|
@@ -2788,6 +2890,25 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2788
2890
|
code: "startup_timeout"
|
|
2789
2891
|
};
|
|
2790
2892
|
}
|
|
2893
|
+
const unexpectedStopResult = this.consumeUnexpectedStopsDuringStartup(
|
|
2894
|
+
startedComponents,
|
|
2895
|
+
failedOptionalComponents
|
|
2896
|
+
);
|
|
2897
|
+
startedComponents.splice(0, startedComponents.length);
|
|
2898
|
+
startedComponents.push(...unexpectedStopResult.startedComponents);
|
|
2899
|
+
if (unexpectedStopResult.requiredFailure) {
|
|
2900
|
+
await this.rollbackStartup(startedComponents);
|
|
2901
|
+
return {
|
|
2902
|
+
success: false,
|
|
2903
|
+
startedComponents: [],
|
|
2904
|
+
failedOptionalComponents,
|
|
2905
|
+
skippedDueToDependency: Array.from(skippedDueToDependency),
|
|
2906
|
+
reason: unexpectedStopResult.requiredFailure.error.message,
|
|
2907
|
+
code: "component_unexpected_stop",
|
|
2908
|
+
error: unexpectedStopResult.requiredFailure.error,
|
|
2909
|
+
durationMS: Date.now() - startTime
|
|
2910
|
+
};
|
|
2911
|
+
}
|
|
2791
2912
|
this.updateStartedFlag();
|
|
2792
2913
|
const skippedComponentsArray = [
|
|
2793
2914
|
...Array.from(skippedDueToDependency),
|
|
@@ -2819,10 +2940,12 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2819
2940
|
if (timeoutHandle) {
|
|
2820
2941
|
clearTimeout(timeoutHandle);
|
|
2821
2942
|
}
|
|
2822
|
-
|
|
2943
|
+
this.isStarting = false;
|
|
2944
|
+
if (didAutoAttachSignalsForBulkStartup || this.autoAttachedSignalsDuringStartup) {
|
|
2823
2945
|
this.autoDetachSignalsIfIdle("failed bulk startup");
|
|
2824
2946
|
}
|
|
2825
|
-
this.
|
|
2947
|
+
this.autoAttachedSignalsDuringStartup = false;
|
|
2948
|
+
this.unexpectedStopsDuringStartup.clear();
|
|
2826
2949
|
}
|
|
2827
2950
|
}
|
|
2828
2951
|
/**
|
|
@@ -2886,7 +3009,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2886
3009
|
return {
|
|
2887
3010
|
success: false,
|
|
2888
3011
|
componentName: name,
|
|
2889
|
-
reason:
|
|
3012
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_BULK_STARTUP_IN_PROGRESS,
|
|
2890
3013
|
code: "startup_in_progress"
|
|
2891
3014
|
};
|
|
2892
3015
|
}
|
|
@@ -2897,7 +3020,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2897
3020
|
return {
|
|
2898
3021
|
success: false,
|
|
2899
3022
|
componentName: name,
|
|
2900
|
-
reason:
|
|
3023
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_SHUTDOWN_IN_PROGRESS,
|
|
2901
3024
|
code: "shutdown_in_progress"
|
|
2902
3025
|
};
|
|
2903
3026
|
}
|
|
@@ -2931,7 +3054,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2931
3054
|
return {
|
|
2932
3055
|
success: false,
|
|
2933
3056
|
componentName: name,
|
|
2934
|
-
reason: this.isStarting ?
|
|
3057
|
+
reason: this.isStarting ? LIFECYCLE_MANAGER_MESSAGE_BULK_STARTUP_IN_PROGRESS : LIFECYCLE_MANAGER_MESSAGE_SHUTDOWN_IN_PROGRESS,
|
|
2935
3058
|
code: this.isStarting ? "startup_in_progress" : "shutdown_in_progress"
|
|
2936
3059
|
};
|
|
2937
3060
|
}
|
|
@@ -3113,7 +3236,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3113
3236
|
if (this.isShuttingDown) {
|
|
3114
3237
|
if (isFirstExit && this.pendingLoggerExitResolve === null) {
|
|
3115
3238
|
this.logger.debug(
|
|
3116
|
-
|
|
3239
|
+
LIFECYCLE_MANAGER_LOG_LOGGER_EXIT_DURING_SHUTDOWN,
|
|
3117
3240
|
{
|
|
3118
3241
|
params: { exitCode }
|
|
3119
3242
|
}
|
|
@@ -3122,7 +3245,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3122
3245
|
this.pendingLoggerExitResolve = resolve;
|
|
3123
3246
|
});
|
|
3124
3247
|
}
|
|
3125
|
-
this.logger.debug(
|
|
3248
|
+
this.logger.debug(LIFECYCLE_MANAGER_LOG_LOGGER_EXIT_DURING_SHUTDOWN, {
|
|
3126
3249
|
params: { exitCode }
|
|
3127
3250
|
});
|
|
3128
3251
|
return { action: "wait" };
|
|
@@ -3212,7 +3335,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3212
3335
|
return {
|
|
3213
3336
|
name,
|
|
3214
3337
|
healthy: false,
|
|
3215
|
-
message:
|
|
3338
|
+
message: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_FOUND,
|
|
3216
3339
|
checkedAt: startTime,
|
|
3217
3340
|
durationMS: 0,
|
|
3218
3341
|
error: null,
|
|
@@ -3225,7 +3348,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3225
3348
|
return {
|
|
3226
3349
|
name,
|
|
3227
3350
|
healthy: false,
|
|
3228
|
-
message: isStalled ?
|
|
3351
|
+
message: isStalled ? LIFECYCLE_MANAGER_MESSAGE_COMPONENT_STALLED : LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_RUNNING,
|
|
3229
3352
|
checkedAt: startTime,
|
|
3230
3353
|
durationMS: Date.now() - startTime,
|
|
3231
3354
|
error: null,
|
|
@@ -3441,7 +3564,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3441
3564
|
result = component.onMessage(payload, from);
|
|
3442
3565
|
} catch (error) {
|
|
3443
3566
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
3444
|
-
this.logger.entity(componentName).error(
|
|
3567
|
+
this.logger.entity(componentName).error(LIFECYCLE_MANAGER_LOG_MESSAGE_HANDLER_FAILED, {
|
|
3445
3568
|
params: { error: err, from }
|
|
3446
3569
|
});
|
|
3447
3570
|
this.lifecycleEvents.componentMessageFailed(componentName, from, err, {
|
|
@@ -3501,7 +3624,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3501
3624
|
};
|
|
3502
3625
|
} catch (error) {
|
|
3503
3626
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
3504
|
-
this.logger.entity(componentName).error(
|
|
3627
|
+
this.logger.entity(componentName).error(LIFECYCLE_MANAGER_LOG_MESSAGE_HANDLER_FAILED, {
|
|
3505
3628
|
params: { error: err, from, timeoutMS }
|
|
3506
3629
|
});
|
|
3507
3630
|
this.lifecycleEvents.componentMessageFailed(componentName, from, err, {
|
|
@@ -3774,7 +3897,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3774
3897
|
this.lifecycleEvents.componentRegistrationRejected({
|
|
3775
3898
|
name: componentName,
|
|
3776
3899
|
reason: "shutdown_in_progress",
|
|
3777
|
-
message:
|
|
3900
|
+
message: LIFECYCLE_MANAGER_MESSAGE_REGISTER_SHUTDOWN_IN_PROGRESS,
|
|
3778
3901
|
registrationIndexBefore,
|
|
3779
3902
|
registrationIndexAfter: registrationIndexBefore,
|
|
3780
3903
|
requestedPosition: isInsertAction ? { position, targetComponentName } : void 0,
|
|
@@ -3786,7 +3909,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3786
3909
|
targetComponentName,
|
|
3787
3910
|
registrationIndexBefore,
|
|
3788
3911
|
code: "shutdown_in_progress",
|
|
3789
|
-
reason:
|
|
3912
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_REGISTER_SHUTDOWN_IN_PROGRESS,
|
|
3790
3913
|
targetFound: void 0
|
|
3791
3914
|
});
|
|
3792
3915
|
}
|
|
@@ -3797,7 +3920,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3797
3920
|
this.lifecycleEvents.componentRegistrationRejected({
|
|
3798
3921
|
name: componentName,
|
|
3799
3922
|
reason: "startup_in_progress",
|
|
3800
|
-
message:
|
|
3923
|
+
message: LIFECYCLE_MANAGER_MESSAGE_REGISTER_REQUIRED_DEPENDENCY_DURING_STARTUP,
|
|
3801
3924
|
registrationIndexBefore,
|
|
3802
3925
|
registrationIndexAfter: registrationIndexBefore,
|
|
3803
3926
|
requestedPosition: isInsertAction ? { position, targetComponentName } : void 0,
|
|
@@ -3809,16 +3932,20 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3809
3932
|
targetComponentName,
|
|
3810
3933
|
registrationIndexBefore,
|
|
3811
3934
|
code: "startup_in_progress",
|
|
3812
|
-
reason:
|
|
3935
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_REGISTER_REQUIRED_DEPENDENCY_DURING_STARTUP,
|
|
3813
3936
|
targetFound: void 0
|
|
3814
3937
|
});
|
|
3815
3938
|
}
|
|
3816
|
-
if (
|
|
3817
|
-
this.
|
|
3939
|
+
if (component._isRegisteredWithManager()) {
|
|
3940
|
+
const isRegisteredHere = this.hasComponentInstance(component);
|
|
3941
|
+
const message = isRegisteredHere ? LIFECYCLE_MANAGER_MESSAGE_DUPLICATE_COMPONENT_INSTANCE : LIFECYCLE_MANAGER_MESSAGE_DUPLICATE_COMPONENT_INSTANCE_EXTERNAL;
|
|
3942
|
+
this.logger.entity(componentName).warn(
|
|
3943
|
+
isRegisteredHere ? "Component instance already registered" : "Component instance already registered with another lifecycle manager"
|
|
3944
|
+
);
|
|
3818
3945
|
this.lifecycleEvents.componentRegistrationRejected({
|
|
3819
3946
|
name: componentName,
|
|
3820
3947
|
reason: "duplicate_instance",
|
|
3821
|
-
message
|
|
3948
|
+
message,
|
|
3822
3949
|
registrationIndexBefore,
|
|
3823
3950
|
registrationIndexAfter: registrationIndexBefore,
|
|
3824
3951
|
requestedPosition: isInsertAction ? { position, targetComponentName } : void 0,
|
|
@@ -3830,7 +3957,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3830
3957
|
targetComponentName,
|
|
3831
3958
|
registrationIndexBefore,
|
|
3832
3959
|
code: "duplicate_instance",
|
|
3833
|
-
reason:
|
|
3960
|
+
reason: message,
|
|
3834
3961
|
targetFound: void 0
|
|
3835
3962
|
});
|
|
3836
3963
|
}
|
|
@@ -3943,6 +4070,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3943
4070
|
getValueInternal: (compName, key, from) => this.getValueInternal(compName, key, from)
|
|
3944
4071
|
};
|
|
3945
4072
|
component.lifecycle = new ComponentLifecycle(this, componentName, internalCallbacks);
|
|
4073
|
+
component._markRegistered();
|
|
3946
4074
|
this.componentStates.set(componentName, "registered");
|
|
3947
4075
|
this.componentTimestamps.set(componentName, {
|
|
3948
4076
|
startedAt: null,
|
|
@@ -4144,8 +4272,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4144
4272
|
const runningComponentsToStop = shutdownOrder.filter(
|
|
4145
4273
|
(name) => this.isComponentRunning(name) || shouldRetryStalled && stalledComponentNames.has(name)
|
|
4146
4274
|
);
|
|
4147
|
-
const stoppedComponents =
|
|
4148
|
-
const stalledComponents = [];
|
|
4275
|
+
const stoppedComponents = /* @__PURE__ */ new Set();
|
|
4149
4276
|
let hasTimedOut = false;
|
|
4150
4277
|
let timeoutHandle;
|
|
4151
4278
|
try {
|
|
@@ -4176,34 +4303,37 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4176
4303
|
this.logger.entity(name).info("Stopping component");
|
|
4177
4304
|
const isRunning = this.isComponentRunning(name);
|
|
4178
4305
|
const isStalled = stalledComponentNames.has(name);
|
|
4306
|
+
const currentState = this.componentStates.get(name);
|
|
4307
|
+
if (currentState === "stopped") {
|
|
4308
|
+
stoppedComponents.add(name);
|
|
4309
|
+
continue;
|
|
4310
|
+
}
|
|
4179
4311
|
const result2 = isRunning ? await this.stopComponentInternal(name) : shouldRetryStalled && isStalled ? await this.retryStalledComponent(name) : isStalled ? {
|
|
4180
4312
|
success: false,
|
|
4181
4313
|
componentName: name,
|
|
4182
|
-
reason:
|
|
4314
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_STALLED,
|
|
4183
4315
|
code: "component_stalled",
|
|
4184
4316
|
status: this.getComponentStatus(name)
|
|
4185
4317
|
} : {
|
|
4186
4318
|
success: false,
|
|
4187
4319
|
componentName: name,
|
|
4188
|
-
reason:
|
|
4320
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_RUNNING,
|
|
4189
4321
|
code: "component_not_running",
|
|
4190
4322
|
status: this.getComponentStatus(name)
|
|
4191
4323
|
};
|
|
4192
4324
|
if (result2.success) {
|
|
4193
|
-
stoppedComponents.
|
|
4325
|
+
stoppedComponents.add(name);
|
|
4194
4326
|
} else {
|
|
4195
4327
|
this.logger.entity(name).error(
|
|
4196
4328
|
"Component failed to stop, continuing with others: {{error.message}}",
|
|
4197
4329
|
{
|
|
4198
4330
|
params: {
|
|
4199
|
-
error: result2.error || new Error(
|
|
4331
|
+
error: result2.error || new Error(
|
|
4332
|
+
result2.reason || LIFECYCLE_MANAGER_MESSAGE_UNKNOWN_ERROR
|
|
4333
|
+
)
|
|
4200
4334
|
}
|
|
4201
4335
|
}
|
|
4202
4336
|
);
|
|
4203
|
-
const stallInfo = this.stalledComponents.get(name);
|
|
4204
|
-
if (stallInfo) {
|
|
4205
|
-
stalledComponents.push(stallInfo);
|
|
4206
|
-
}
|
|
4207
4337
|
if (shouldHaltOnStall) {
|
|
4208
4338
|
this.logger.warn(
|
|
4209
4339
|
"Halting shutdown after stall (haltOnStall=true)",
|
|
@@ -4219,21 +4349,32 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4219
4349
|
} else {
|
|
4220
4350
|
await shutdownOperation();
|
|
4221
4351
|
}
|
|
4352
|
+
const finalStalledNames = /* @__PURE__ */ new Set();
|
|
4353
|
+
for (const name of runningComponentsToStop) {
|
|
4354
|
+
if (this.stalledComponents.has(name)) {
|
|
4355
|
+
finalStalledNames.add(name);
|
|
4356
|
+
}
|
|
4357
|
+
}
|
|
4222
4358
|
if (!shouldRetryStalled) {
|
|
4223
4359
|
for (const name of stalledComponentNames) {
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
stalledComponents.push(stallInfo);
|
|
4360
|
+
if (this.stalledComponents.has(name)) {
|
|
4361
|
+
finalStalledNames.add(name);
|
|
4227
4362
|
}
|
|
4228
4363
|
}
|
|
4229
4364
|
}
|
|
4365
|
+
for (const name of runningComponentsToStop) {
|
|
4366
|
+
if (!finalStalledNames.has(name) && this.componentStates.get(name) === "stopped") {
|
|
4367
|
+
stoppedComponents.add(name);
|
|
4368
|
+
}
|
|
4369
|
+
}
|
|
4370
|
+
const stalledComponents = Array.from(finalStalledNames).map((name) => this.stalledComponents.get(name)).filter((stallInfo) => !!stallInfo);
|
|
4230
4371
|
const durationMS = Date.now() - startTime;
|
|
4231
4372
|
const isSuccess = !hasTimedOut && stalledComponents.length === 0;
|
|
4232
4373
|
this.logger[isSuccess ? "success" : "warn"](
|
|
4233
4374
|
isSuccess ? "Shutdown completed successfully" : "Shutdown attempt completed with stalled components or timeout",
|
|
4234
4375
|
{
|
|
4235
4376
|
params: {
|
|
4236
|
-
stopped: stoppedComponents.
|
|
4377
|
+
stopped: stoppedComponents.size,
|
|
4237
4378
|
stalled: stalledComponents.length,
|
|
4238
4379
|
durationMS
|
|
4239
4380
|
}
|
|
@@ -4241,7 +4382,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4241
4382
|
);
|
|
4242
4383
|
const result = {
|
|
4243
4384
|
success: isSuccess,
|
|
4244
|
-
stoppedComponents,
|
|
4385
|
+
stoppedComponents: Array.from(stoppedComponents),
|
|
4245
4386
|
stalledComponents,
|
|
4246
4387
|
durationMS,
|
|
4247
4388
|
timedOut: hasTimedOut || void 0,
|
|
@@ -4292,7 +4433,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4292
4433
|
return {
|
|
4293
4434
|
success: false,
|
|
4294
4435
|
componentName: name,
|
|
4295
|
-
reason:
|
|
4436
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_FOUND,
|
|
4296
4437
|
code: "component_not_found"
|
|
4297
4438
|
};
|
|
4298
4439
|
}
|
|
@@ -4303,12 +4444,15 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4303
4444
|
return {
|
|
4304
4445
|
success: false,
|
|
4305
4446
|
componentName: name,
|
|
4306
|
-
reason:
|
|
4447
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_RUNNING,
|
|
4307
4448
|
code: "component_not_running",
|
|
4308
4449
|
status: this.getComponentStatus(name)
|
|
4309
4450
|
};
|
|
4310
4451
|
}
|
|
4311
4452
|
this.logger.entity(name).warn("Retrying stalled component shutdown (force phase)");
|
|
4453
|
+
if (component.onShutdownForce) {
|
|
4454
|
+
this.issueStopAttemptToken(name);
|
|
4455
|
+
}
|
|
4312
4456
|
return this.shutdownComponentForce(name, component, {
|
|
4313
4457
|
gracefulPhaseRan: false,
|
|
4314
4458
|
gracefulTimedOut: false,
|
|
@@ -4328,7 +4472,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4328
4472
|
return {
|
|
4329
4473
|
success: false,
|
|
4330
4474
|
componentName: name,
|
|
4331
|
-
reason:
|
|
4475
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_SHUTDOWN_IN_PROGRESS,
|
|
4332
4476
|
code: "shutdown_in_progress"
|
|
4333
4477
|
};
|
|
4334
4478
|
}
|
|
@@ -4340,7 +4484,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4340
4484
|
return {
|
|
4341
4485
|
success: false,
|
|
4342
4486
|
componentName: name,
|
|
4343
|
-
reason:
|
|
4487
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_BULK_STARTUP_IN_PROGRESS,
|
|
4344
4488
|
code: "startup_in_progress"
|
|
4345
4489
|
};
|
|
4346
4490
|
}
|
|
@@ -4350,7 +4494,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4350
4494
|
return {
|
|
4351
4495
|
success: false,
|
|
4352
4496
|
componentName: name,
|
|
4353
|
-
reason:
|
|
4497
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_FOUND,
|
|
4354
4498
|
code: "component_not_found"
|
|
4355
4499
|
};
|
|
4356
4500
|
}
|
|
@@ -4359,7 +4503,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4359
4503
|
return {
|
|
4360
4504
|
success: false,
|
|
4361
4505
|
componentName: name,
|
|
4362
|
-
reason:
|
|
4506
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_STALLED,
|
|
4363
4507
|
code: "component_stalled",
|
|
4364
4508
|
status: this.getComponentStatus(name)
|
|
4365
4509
|
};
|
|
@@ -4423,6 +4567,9 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4423
4567
|
const timeoutMS = component.startupTimeoutMS;
|
|
4424
4568
|
const startAttemptToken = ulid2();
|
|
4425
4569
|
this.componentStartAttemptTokens.set(name, startAttemptToken);
|
|
4570
|
+
component._setUnexpectedStopHandler(
|
|
4571
|
+
(error) => this.handleComponentUnexpectedStop(name, startAttemptToken, error)
|
|
4572
|
+
);
|
|
4426
4573
|
const shutdownTokenAtStart = this.shutdownToken;
|
|
4427
4574
|
const didAutoAttachSignalsForComponentStartup = this.attachSignalsBeforeStartup ? this.autoAttachSignals("component startup") : false;
|
|
4428
4575
|
let timeoutHandle;
|
|
@@ -4465,6 +4612,18 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4465
4612
|
} else {
|
|
4466
4613
|
await startPromise;
|
|
4467
4614
|
}
|
|
4615
|
+
if (this.componentStartAttemptTokens.get(name) === startAttemptToken && this.componentStates.get(name) === "stopped" && !this.runningComponents.has(name)) {
|
|
4616
|
+
component._clearUnexpectedStopHandler();
|
|
4617
|
+
const error = this.componentErrors.get(name) ?? new Error(`Component "${name}" stopped unexpectedly during startup`);
|
|
4618
|
+
return {
|
|
4619
|
+
success: false,
|
|
4620
|
+
componentName: name,
|
|
4621
|
+
reason: error.message,
|
|
4622
|
+
code: "component_unexpected_stop",
|
|
4623
|
+
error,
|
|
4624
|
+
status: this.getComponentStatus(name)
|
|
4625
|
+
};
|
|
4626
|
+
}
|
|
4468
4627
|
if (this.isShuttingDown || shutdownTokenAtStart !== this.shutdownToken) {
|
|
4469
4628
|
this.componentStates.set(name, "running");
|
|
4470
4629
|
this.runningComponents.add(name);
|
|
@@ -4492,6 +4651,9 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4492
4651
|
this.componentStates.set(name, "running");
|
|
4493
4652
|
this.runningComponents.add(name);
|
|
4494
4653
|
this.stalledComponents.delete(name);
|
|
4654
|
+
if (shouldForceStalled) {
|
|
4655
|
+
this.issueStopAttemptToken(name);
|
|
4656
|
+
}
|
|
4495
4657
|
this.updateStartedFlag();
|
|
4496
4658
|
if (this.attachSignalsOnStart && this.runningComponents.size === 1) {
|
|
4497
4659
|
this.autoAttachSignals("first component start");
|
|
@@ -4511,9 +4673,24 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4511
4673
|
status: this.getComponentStatus(name)
|
|
4512
4674
|
};
|
|
4513
4675
|
} catch (error) {
|
|
4676
|
+
component._clearUnexpectedStopHandler();
|
|
4514
4677
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
4678
|
+
const isStartupTimeout = err instanceof ComponentStartTimeoutError && err.additionalInfo.componentName === name;
|
|
4679
|
+
const unexpectedStopError = this.componentErrors.get(name);
|
|
4680
|
+
if (this.componentStartAttemptTokens.get(name) === startAttemptToken && this.componentStates.get(name) === "stopped" && !this.runningComponents.has(name) && (isStartupTimeout || unexpectedStopError instanceof Error)) {
|
|
4681
|
+
return {
|
|
4682
|
+
success: false,
|
|
4683
|
+
componentName: name,
|
|
4684
|
+
reason: unexpectedStopError?.message || `Component "${name}" stopped unexpectedly during startup`,
|
|
4685
|
+
code: "component_unexpected_stop",
|
|
4686
|
+
error: unexpectedStopError || new Error(
|
|
4687
|
+
`Component "${name}" stopped unexpectedly during startup`
|
|
4688
|
+
),
|
|
4689
|
+
status: this.getComponentStatus(name)
|
|
4690
|
+
};
|
|
4691
|
+
}
|
|
4515
4692
|
this.componentErrors.set(name, err);
|
|
4516
|
-
if (
|
|
4693
|
+
if (isStartupTimeout) {
|
|
4517
4694
|
this.componentStates.set(name, "starting-timed-out");
|
|
4518
4695
|
this.logger.entity(name).error("Component startup timed out: {{error.message}}", {
|
|
4519
4696
|
params: { error: err }
|
|
@@ -4558,7 +4735,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4558
4735
|
return {
|
|
4559
4736
|
success: false,
|
|
4560
4737
|
componentName: name,
|
|
4561
|
-
reason:
|
|
4738
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_FOUND,
|
|
4562
4739
|
code: "component_not_found"
|
|
4563
4740
|
};
|
|
4564
4741
|
}
|
|
@@ -4566,7 +4743,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4566
4743
|
return {
|
|
4567
4744
|
success: false,
|
|
4568
4745
|
componentName: name,
|
|
4569
|
-
reason:
|
|
4746
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_STALLED,
|
|
4570
4747
|
code: "component_stalled",
|
|
4571
4748
|
status: this.getComponentStatus(name)
|
|
4572
4749
|
};
|
|
@@ -4575,7 +4752,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4575
4752
|
return {
|
|
4576
4753
|
success: false,
|
|
4577
4754
|
componentName: name,
|
|
4578
|
-
reason:
|
|
4755
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_RUNNING,
|
|
4579
4756
|
code: "component_not_running",
|
|
4580
4757
|
status: this.getComponentStatus(name)
|
|
4581
4758
|
};
|
|
@@ -4591,6 +4768,10 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4591
4768
|
};
|
|
4592
4769
|
}
|
|
4593
4770
|
if (options?.forceImmediate) {
|
|
4771
|
+
if (component.onShutdownForce) {
|
|
4772
|
+
this.issueStopAttemptToken(name);
|
|
4773
|
+
}
|
|
4774
|
+
component._clearUnexpectedStopHandler();
|
|
4594
4775
|
return this.shutdownComponentForce(name, component, {
|
|
4595
4776
|
gracefulPhaseRan: false,
|
|
4596
4777
|
gracefulTimedOut: false,
|
|
@@ -4727,9 +4908,11 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4727
4908
|
* Calls stop() with timeout
|
|
4728
4909
|
*/
|
|
4729
4910
|
async shutdownComponentGraceful(name, component, options) {
|
|
4911
|
+
component._clearUnexpectedStopHandler();
|
|
4730
4912
|
this.componentStates.set(name, "stopping");
|
|
4731
4913
|
this.logger.entity(name).info("Graceful shutdown started");
|
|
4732
4914
|
this.lifecycleEvents.componentStopping(name);
|
|
4915
|
+
const stopAttemptToken = this.issueStopAttemptToken(name);
|
|
4733
4916
|
const timeoutMS = options?.timeout ?? component.shutdownGracefulTimeoutMS;
|
|
4734
4917
|
let timeoutHandle;
|
|
4735
4918
|
try {
|
|
@@ -4750,7 +4933,16 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4750
4933
|
);
|
|
4751
4934
|
}
|
|
4752
4935
|
}
|
|
4753
|
-
Promise.resolve(stopPromise).
|
|
4936
|
+
Promise.resolve(stopPromise).then(
|
|
4937
|
+
() => this.handleLateStopResolution(
|
|
4938
|
+
name,
|
|
4939
|
+
stopAttemptToken,
|
|
4940
|
+
"graceful"
|
|
4941
|
+
),
|
|
4942
|
+
() => {
|
|
4943
|
+
}
|
|
4944
|
+
// Intentionally ignore errors after timeout
|
|
4945
|
+
).catch(() => {
|
|
4754
4946
|
});
|
|
4755
4947
|
reject(
|
|
4756
4948
|
new ComponentStopTimeoutError({
|
|
@@ -4769,9 +4961,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4769
4961
|
this.stalledComponents.delete(name);
|
|
4770
4962
|
this.updateStartedFlag();
|
|
4771
4963
|
if (this.detachSignalsOnStop && this.runningComponents.size === 0 && this.processSignalManager) {
|
|
4772
|
-
this.logger.info(
|
|
4773
|
-
"Auto-detaching process signals on last component stop"
|
|
4774
|
-
);
|
|
4964
|
+
this.logger.info(LIFECYCLE_MANAGER_LOG_AUTO_DETACH_LAST_COMPONENT_STOP);
|
|
4775
4965
|
this.detachSignals();
|
|
4776
4966
|
}
|
|
4777
4967
|
const timestamps = this.componentTimestamps.get(name) ?? {
|
|
@@ -4794,15 +4984,15 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4794
4984
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
4795
4985
|
this.componentErrors.set(name, err);
|
|
4796
4986
|
if (err instanceof ComponentStopTimeoutError && err.additionalInfo.componentName === name) {
|
|
4797
|
-
this.logger.entity(name).warn(
|
|
4987
|
+
this.logger.entity(name).warn(LIFECYCLE_MANAGER_MESSAGE_GRACEFUL_SHUTDOWN_TIMED_OUT);
|
|
4798
4988
|
this.lifecycleEvents.componentStopTimeout(name, err, {
|
|
4799
4989
|
timeoutMS,
|
|
4800
|
-
reason:
|
|
4990
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_GRACEFUL_SHUTDOWN_TIMED_OUT
|
|
4801
4991
|
});
|
|
4802
4992
|
return {
|
|
4803
4993
|
success: false,
|
|
4804
4994
|
componentName: name,
|
|
4805
|
-
reason:
|
|
4995
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_GRACEFUL_SHUTDOWN_TIMED_OUT,
|
|
4806
4996
|
code: "component_shutdown_timeout",
|
|
4807
4997
|
error: err,
|
|
4808
4998
|
status: this.getComponentStatus(name)
|
|
@@ -4845,8 +5035,6 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4845
5035
|
gracefulTimedOut: context.gracefulTimedOut
|
|
4846
5036
|
}
|
|
4847
5037
|
});
|
|
4848
|
-
const timeoutMS = component.shutdownForceTimeoutMS;
|
|
4849
|
-
let timeoutHandle;
|
|
4850
5038
|
if (!component.onShutdownForce) {
|
|
4851
5039
|
const stallInfo = {
|
|
4852
5040
|
name,
|
|
@@ -4880,6 +5068,9 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4880
5068
|
status: this.getComponentStatus(name)
|
|
4881
5069
|
};
|
|
4882
5070
|
}
|
|
5071
|
+
const timeoutMS = component.shutdownForceTimeoutMS;
|
|
5072
|
+
const { promise: stoppedDuringForcePromise, cleanup: cleanupForceWaiter } = this.createPendingForceStopWaiter(name);
|
|
5073
|
+
let timeoutHandle;
|
|
4883
5074
|
try {
|
|
4884
5075
|
const forcePromise = component.onShutdownForce();
|
|
4885
5076
|
if (timeoutMS > 0) {
|
|
@@ -4898,23 +5089,44 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4898
5089
|
);
|
|
4899
5090
|
}
|
|
4900
5091
|
}
|
|
4901
|
-
|
|
5092
|
+
const forceAttemptToken = this.componentStopAttemptTokens.get(name) ?? ulid2();
|
|
5093
|
+
Promise.resolve(forcePromise).then(
|
|
5094
|
+
() => this.handleLateStopResolution(
|
|
5095
|
+
name,
|
|
5096
|
+
forceAttemptToken,
|
|
5097
|
+
"force"
|
|
5098
|
+
),
|
|
5099
|
+
() => {
|
|
5100
|
+
}
|
|
5101
|
+
// Intentionally ignore errors after timeout
|
|
5102
|
+
).catch(() => {
|
|
4902
5103
|
});
|
|
4903
|
-
reject(
|
|
5104
|
+
reject(
|
|
5105
|
+
new Error(LIFECYCLE_MANAGER_MESSAGE_FORCE_SHUTDOWN_TIMED_OUT)
|
|
5106
|
+
);
|
|
4904
5107
|
}, timeoutMS);
|
|
4905
5108
|
});
|
|
4906
|
-
await Promise.race([
|
|
5109
|
+
await Promise.race([
|
|
5110
|
+
forcePromise,
|
|
5111
|
+
timeoutPromise,
|
|
5112
|
+
stoppedDuringForcePromise
|
|
5113
|
+
]);
|
|
4907
5114
|
} else {
|
|
4908
|
-
await forcePromise;
|
|
5115
|
+
await Promise.race([forcePromise, stoppedDuringForcePromise]);
|
|
5116
|
+
}
|
|
5117
|
+
if (this.componentStates.get(name) === "stopped" && !this.runningComponents.has(name)) {
|
|
5118
|
+
return {
|
|
5119
|
+
success: true,
|
|
5120
|
+
componentName: name,
|
|
5121
|
+
status: this.getComponentStatus(name)
|
|
5122
|
+
};
|
|
4909
5123
|
}
|
|
4910
5124
|
this.componentStates.set(name, "stopped");
|
|
4911
5125
|
this.runningComponents.delete(name);
|
|
4912
5126
|
this.stalledComponents.delete(name);
|
|
4913
5127
|
this.updateStartedFlag();
|
|
4914
5128
|
if (this.detachSignalsOnStop && this.runningComponents.size === 0 && this.processSignalManager) {
|
|
4915
|
-
this.logger.info(
|
|
4916
|
-
"Auto-detaching process signals on last component stop"
|
|
4917
|
-
);
|
|
5129
|
+
this.logger.info(LIFECYCLE_MANAGER_LOG_AUTO_DETACH_LAST_COMPONENT_STOP);
|
|
4918
5130
|
this.detachSignals();
|
|
4919
5131
|
}
|
|
4920
5132
|
const timestamps = this.componentTimestamps.get(name) ?? {
|
|
@@ -4935,8 +5147,15 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4935
5147
|
status: this.getComponentStatus(name)
|
|
4936
5148
|
};
|
|
4937
5149
|
} catch (error) {
|
|
5150
|
+
if (this.componentStates.get(name) === "stopped" && !this.runningComponents.has(name)) {
|
|
5151
|
+
return {
|
|
5152
|
+
success: true,
|
|
5153
|
+
componentName: name,
|
|
5154
|
+
status: this.getComponentStatus(name)
|
|
5155
|
+
};
|
|
5156
|
+
}
|
|
4938
5157
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
4939
|
-
const isTimeout = err.message ===
|
|
5158
|
+
const isTimeout = err.message === LIFECYCLE_MANAGER_MESSAGE_FORCE_SHUTDOWN_TIMED_OUT;
|
|
4940
5159
|
const stallInfo = {
|
|
4941
5160
|
name,
|
|
4942
5161
|
phase: "force",
|
|
@@ -4967,12 +5186,13 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4967
5186
|
return {
|
|
4968
5187
|
success: false,
|
|
4969
5188
|
componentName: name,
|
|
4970
|
-
reason: isTimeout ?
|
|
5189
|
+
reason: isTimeout ? LIFECYCLE_MANAGER_MESSAGE_FORCE_SHUTDOWN_TIMED_OUT : err.message,
|
|
4971
5190
|
code: isTimeout ? "component_shutdown_timeout" : "unknown_error",
|
|
4972
5191
|
error: err,
|
|
4973
5192
|
status: this.getComponentStatus(name)
|
|
4974
5193
|
};
|
|
4975
5194
|
} finally {
|
|
5195
|
+
cleanupForceWaiter();
|
|
4976
5196
|
if (timeoutHandle) {
|
|
4977
5197
|
clearTimeout(timeoutHandle);
|
|
4978
5198
|
}
|
|
@@ -5049,7 +5269,9 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
5049
5269
|
"Failed to stop component during rollback, continuing: {{error.message}}",
|
|
5050
5270
|
{
|
|
5051
5271
|
params: {
|
|
5052
|
-
error: result.error || new Error(
|
|
5272
|
+
error: result.error || new Error(
|
|
5273
|
+
result.reason || LIFECYCLE_MANAGER_MESSAGE_UNKNOWN_ERROR
|
|
5274
|
+
)
|
|
5053
5275
|
}
|
|
5054
5276
|
}
|
|
5055
5277
|
);
|
|
@@ -5063,10 +5285,13 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
5063
5285
|
}
|
|
5064
5286
|
this.logger.info(`Auto-attaching process signals on ${trigger}`);
|
|
5065
5287
|
this.attachSignals();
|
|
5288
|
+
if (this.isStarting) {
|
|
5289
|
+
this.autoAttachedSignalsDuringStartup = true;
|
|
5290
|
+
}
|
|
5066
5291
|
return true;
|
|
5067
5292
|
}
|
|
5068
5293
|
autoDetachSignalsIfIdle(trigger) {
|
|
5069
|
-
if (!this.detachSignalsOnStop || this.runningComponents.size > 0 || !this.processSignalManager?.getStatus().isAttached) {
|
|
5294
|
+
if (!this.detachSignalsOnStop || this.isStarting || this.runningComponents.size > 0 || !this.processSignalManager?.getStatus().isAttached) {
|
|
5070
5295
|
return;
|
|
5071
5296
|
}
|
|
5072
5297
|
this.logger.info(`Auto-detaching process signals after ${trigger}`);
|
|
@@ -5108,6 +5333,210 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
5108
5333
|
}).catch(() => {
|
|
5109
5334
|
});
|
|
5110
5335
|
}
|
|
5336
|
+
consumeUnexpectedStopsDuringStartup(startedComponents, failedOptionalComponents) {
|
|
5337
|
+
if (this.unexpectedStopsDuringStartup.size === 0) {
|
|
5338
|
+
return { startedComponents: [...startedComponents] };
|
|
5339
|
+
}
|
|
5340
|
+
const remainingStartedComponents = [];
|
|
5341
|
+
let requiredFailure;
|
|
5342
|
+
for (const name of startedComponents) {
|
|
5343
|
+
const startupStopError = this.unexpectedStopsDuringStartup.get(name);
|
|
5344
|
+
if (startupStopError === void 0) {
|
|
5345
|
+
remainingStartedComponents.push(name);
|
|
5346
|
+
continue;
|
|
5347
|
+
}
|
|
5348
|
+
this.unexpectedStopsDuringStartup.delete(name);
|
|
5349
|
+
const error = startupStopError ?? new Error(`Component "${name}" stopped unexpectedly during startup`);
|
|
5350
|
+
const component = this.getComponent(name);
|
|
5351
|
+
if (component?.isOptional()) {
|
|
5352
|
+
if (!failedOptionalComponents.some((entry) => entry.name === name)) {
|
|
5353
|
+
failedOptionalComponents.push({ name, error });
|
|
5354
|
+
}
|
|
5355
|
+
this.logger.entity(name).warn(
|
|
5356
|
+
LIFECYCLE_MANAGER_LOG_OPTIONAL_COMPONENT_UNEXPECTED_STOP_DURING_STARTUP,
|
|
5357
|
+
{
|
|
5358
|
+
params: { error }
|
|
5359
|
+
}
|
|
5360
|
+
);
|
|
5361
|
+
continue;
|
|
5362
|
+
}
|
|
5363
|
+
this.logger.entity(name).error(
|
|
5364
|
+
LIFECYCLE_MANAGER_LOG_REQUIRED_COMPONENT_UNEXPECTED_STOP_DURING_STARTUP,
|
|
5365
|
+
{
|
|
5366
|
+
params: { error }
|
|
5367
|
+
}
|
|
5368
|
+
);
|
|
5369
|
+
requiredFailure ??= { name, error };
|
|
5370
|
+
}
|
|
5371
|
+
return {
|
|
5372
|
+
startedComponents: remainingStartedComponents,
|
|
5373
|
+
requiredFailure
|
|
5374
|
+
};
|
|
5375
|
+
}
|
|
5376
|
+
/**
|
|
5377
|
+
* Issues and returns a unique stop attempt token for a component.
|
|
5378
|
+
*
|
|
5379
|
+
* Each stop attempt (graceful or force-retry) gets a unique token.
|
|
5380
|
+
* The late-resolution handler captures this token in its closure so it can
|
|
5381
|
+
* skip any stall entries that were created by a *later* stop attempt — e.g. a
|
|
5382
|
+
* force-retry that also timed out after the original graceful promise floated
|
|
5383
|
+
* in the background.
|
|
5384
|
+
*/
|
|
5385
|
+
issueStopAttemptToken(name) {
|
|
5386
|
+
const next = ulid2();
|
|
5387
|
+
this.componentStopAttemptTokens.set(name, next);
|
|
5388
|
+
return next;
|
|
5389
|
+
}
|
|
5390
|
+
createPendingForceStopWaiter(name) {
|
|
5391
|
+
let isResolved = false;
|
|
5392
|
+
let waiters = this.pendingForceStopWaiters.get(name);
|
|
5393
|
+
if (!waiters) {
|
|
5394
|
+
waiters = /* @__PURE__ */ new Set();
|
|
5395
|
+
this.pendingForceStopWaiters.set(name, waiters);
|
|
5396
|
+
}
|
|
5397
|
+
let resolveWaiter;
|
|
5398
|
+
const promise = new Promise((resolve) => {
|
|
5399
|
+
resolveWaiter = () => {
|
|
5400
|
+
if (isResolved) {
|
|
5401
|
+
return;
|
|
5402
|
+
}
|
|
5403
|
+
isResolved = true;
|
|
5404
|
+
resolve();
|
|
5405
|
+
};
|
|
5406
|
+
});
|
|
5407
|
+
waiters.add(resolveWaiter);
|
|
5408
|
+
return {
|
|
5409
|
+
promise,
|
|
5410
|
+
cleanup: () => {
|
|
5411
|
+
const pending = this.pendingForceStopWaiters.get(name);
|
|
5412
|
+
if (!pending) {
|
|
5413
|
+
return;
|
|
5414
|
+
}
|
|
5415
|
+
pending.delete(resolveWaiter);
|
|
5416
|
+
if (pending.size === 0) {
|
|
5417
|
+
this.pendingForceStopWaiters.delete(name);
|
|
5418
|
+
}
|
|
5419
|
+
}
|
|
5420
|
+
};
|
|
5421
|
+
}
|
|
5422
|
+
resolvePendingForceStopWaiters(name) {
|
|
5423
|
+
const waiters = this.pendingForceStopWaiters.get(name);
|
|
5424
|
+
if (!waiters || waiters.size === 0) {
|
|
5425
|
+
return;
|
|
5426
|
+
}
|
|
5427
|
+
this.pendingForceStopWaiters.delete(name);
|
|
5428
|
+
for (const resolve of waiters) {
|
|
5429
|
+
resolve();
|
|
5430
|
+
}
|
|
5431
|
+
}
|
|
5432
|
+
/**
|
|
5433
|
+
* Called when a stop promise eventually resolves after its timeout path already fired.
|
|
5434
|
+
*
|
|
5435
|
+
* Usually this means a previously stalled component's original stop() or
|
|
5436
|
+
* onShutdownForce() promise finally resolved, so the manager can clear the
|
|
5437
|
+
* stall and transition the component to stopped without a manual retry.
|
|
5438
|
+
*
|
|
5439
|
+
* There is one extra overlap case for graceful stop(): stop() can resolve
|
|
5440
|
+
* after the graceful timeout but before onShutdownForce() itself times out.
|
|
5441
|
+
* In that window no stall entry exists yet, but the component still finished
|
|
5442
|
+
* stopping cleanly, so we finalize it here and let the later force-timeout
|
|
5443
|
+
* path observe the already-stopped state and no-op. This overlap fix is
|
|
5444
|
+
* scoped to the same stop token and will not cross a later retry attempt.
|
|
5445
|
+
*
|
|
5446
|
+
* Two guards prevent stale floating promises from incorrectly clearing state:
|
|
5447
|
+
*
|
|
5448
|
+
* 1. token guard — if a newer stop attempt (e.g. a retryStalled
|
|
5449
|
+
* force-retry) has started since this promise was launched, its token
|
|
5450
|
+
* won't match and we bail out immediately.
|
|
5451
|
+
*
|
|
5452
|
+
* 2. state/stall guard — if the component was unregistered, restarted, or
|
|
5453
|
+
* already cleared by another path, there will be neither a matching stall
|
|
5454
|
+
* entry nor the force-phase overlap state, so we bail out.
|
|
5455
|
+
*/
|
|
5456
|
+
handleLateStopResolution(name, token, source) {
|
|
5457
|
+
if (this.componentStopAttemptTokens.get(name) !== token) {
|
|
5458
|
+
return;
|
|
5459
|
+
}
|
|
5460
|
+
const currentState = this.componentStates.get(name);
|
|
5461
|
+
const stallInfo = this.stalledComponents.get(name);
|
|
5462
|
+
const isCompletedDuringForcePhase = source === "graceful" && !stallInfo && currentState === "force-stopping";
|
|
5463
|
+
if (stallInfo && currentState !== "stalled") {
|
|
5464
|
+
this.stalledComponents.delete(name);
|
|
5465
|
+
return;
|
|
5466
|
+
}
|
|
5467
|
+
if (!stallInfo && !isCompletedDuringForcePhase) {
|
|
5468
|
+
return;
|
|
5469
|
+
}
|
|
5470
|
+
const stalledDurationMS = stallInfo ? Date.now() - stallInfo.stalledAt : void 0;
|
|
5471
|
+
if (stallInfo) {
|
|
5472
|
+
this.stalledComponents.delete(name);
|
|
5473
|
+
}
|
|
5474
|
+
this.componentStates.set(name, "stopped");
|
|
5475
|
+
this.runningComponents.delete(name);
|
|
5476
|
+
this.componentErrors.set(name, null);
|
|
5477
|
+
this.updateStartedFlag();
|
|
5478
|
+
this.resolvePendingForceStopWaiters(name);
|
|
5479
|
+
if (this.detachSignalsOnStop && this.runningComponents.size === 0 && this.processSignalManager) {
|
|
5480
|
+
this.logger.info(LIFECYCLE_MANAGER_LOG_AUTO_DETACH_LAST_COMPONENT_STOP);
|
|
5481
|
+
this.detachSignals();
|
|
5482
|
+
}
|
|
5483
|
+
const timestamps = this.componentTimestamps.get(name) ?? {
|
|
5484
|
+
startedAt: null,
|
|
5485
|
+
stoppedAt: null
|
|
5486
|
+
};
|
|
5487
|
+
timestamps.stoppedAt = Date.now();
|
|
5488
|
+
this.componentTimestamps.set(name, timestamps);
|
|
5489
|
+
this.logger.entity(name).info(
|
|
5490
|
+
stallInfo ? "Stalled component completed stop late, stall cleared" : "Graceful stop completed after force phase started",
|
|
5491
|
+
stalledDurationMS ? { params: { stalledDurationMS } } : void 0
|
|
5492
|
+
);
|
|
5493
|
+
if (source === "force") {
|
|
5494
|
+
this.lifecycleEvents.componentShutdownForceCompleted(name);
|
|
5495
|
+
}
|
|
5496
|
+
if (stallInfo && stalledDurationMS !== void 0) {
|
|
5497
|
+
this.lifecycleEvents.componentStalledResolved(
|
|
5498
|
+
name,
|
|
5499
|
+
stallInfo,
|
|
5500
|
+
stalledDurationMS
|
|
5501
|
+
);
|
|
5502
|
+
}
|
|
5503
|
+
this.lifecycleEvents.componentStopped(name, this.getComponentStatus(name));
|
|
5504
|
+
}
|
|
5505
|
+
handleComponentUnexpectedStop(name, startAttemptToken, error) {
|
|
5506
|
+
const currentState = this.componentStates.get(name);
|
|
5507
|
+
if (
|
|
5508
|
+
// Startup-time self-stops are valid too: start() may still be awaiting
|
|
5509
|
+
// some async work while an internal listener has already observed that
|
|
5510
|
+
// the component died and reported it.
|
|
5511
|
+
currentState !== "starting" && currentState !== "running" || this.componentStartAttemptTokens.get(name) !== startAttemptToken
|
|
5512
|
+
) {
|
|
5513
|
+
return false;
|
|
5514
|
+
}
|
|
5515
|
+
this.runningComponents.delete(name);
|
|
5516
|
+
this.componentStates.set(name, "stopped");
|
|
5517
|
+
this.componentErrors.set(name, error ?? null);
|
|
5518
|
+
if (this.isStarting) {
|
|
5519
|
+
this.unexpectedStopsDuringStartup.set(name, error ?? null);
|
|
5520
|
+
}
|
|
5521
|
+
this.updateStartedFlag();
|
|
5522
|
+
if (this.detachSignalsOnStop && !this.isStarting && 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).warn(
|
|
5533
|
+
error ? `Component stopped unexpectedly: ${error.message}` : "Component stopped unexpectedly",
|
|
5534
|
+
{ params: { error } }
|
|
5535
|
+
);
|
|
5536
|
+
this.lifecycleEvents.componentUnexpectedStop(name, error);
|
|
5537
|
+
this.lifecycleEvents.componentStopped(name, this.getComponentStatus(name));
|
|
5538
|
+
return true;
|
|
5539
|
+
}
|
|
5111
5540
|
/**
|
|
5112
5541
|
* Safe emit wrapper - prevents event handler errors from breaking lifecycle
|
|
5113
5542
|
*/
|
|
@@ -5679,105 +6108,87 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
5679
6108
|
}
|
|
5680
6109
|
}
|
|
5681
6110
|
/**
|
|
5682
|
-
*
|
|
6111
|
+
* Shared dispatch path for reload/info/debug requests. Logs the dispatch,
|
|
6112
|
+
* emits the signal event, then either invokes the user-supplied callback
|
|
6113
|
+
* (passing the broadcast function so the user controls when/whether to
|
|
6114
|
+
* broadcast) or broadcasts directly when no callback is configured.
|
|
5683
6115
|
*
|
|
5684
6116
|
* When called from signal handlers (source='signal'), the Promise is started
|
|
5685
|
-
* but not awaited
|
|
5686
|
-
* still notified and the work completes
|
|
5687
|
-
*
|
|
6117
|
+
* but not awaited — Node.js signal handlers cannot return values, so results
|
|
6118
|
+
* are not accessible. Components are still notified and the work completes.
|
|
5688
6119
|
* When called from manual triggers (source='trigger'), the Promise is awaited
|
|
5689
6120
|
* and results are returned for programmatic use.
|
|
5690
|
-
*
|
|
5691
|
-
* @param source - Whether triggered from signal manager or manual trigger
|
|
5692
6121
|
*/
|
|
5693
|
-
async
|
|
5694
|
-
this.logger.info(
|
|
5695
|
-
|
|
5696
|
-
if (
|
|
5697
|
-
const
|
|
5698
|
-
const result = this.onReloadRequested(broadcastFn);
|
|
6122
|
+
async handleSignalRequest(descriptor, source) {
|
|
6123
|
+
this.logger.info(descriptor.dispatchedLogLabel, { params: { source } });
|
|
6124
|
+
descriptor.emitSignal();
|
|
6125
|
+
if (descriptor.customCallback) {
|
|
6126
|
+
const result = descriptor.customCallback(descriptor.broadcast);
|
|
5699
6127
|
if (isPromise(result)) {
|
|
5700
6128
|
await result;
|
|
5701
6129
|
}
|
|
5702
6130
|
return {
|
|
5703
|
-
signal:
|
|
6131
|
+
signal: descriptor.signal,
|
|
5704
6132
|
results: [],
|
|
5705
6133
|
timedOut: false,
|
|
5706
6134
|
code: "ok"
|
|
5707
6135
|
};
|
|
5708
6136
|
}
|
|
5709
|
-
return
|
|
6137
|
+
return descriptor.broadcast();
|
|
6138
|
+
}
|
|
6139
|
+
async handleReloadRequest(source = "trigger") {
|
|
6140
|
+
return this.handleSignalRequest(
|
|
6141
|
+
{
|
|
6142
|
+
signal: "reload",
|
|
6143
|
+
dispatchedLogLabel: "Reload dispatched",
|
|
6144
|
+
emitSignal: () => this.lifecycleEvents.signalReload(),
|
|
6145
|
+
customCallback: this.onReloadRequested,
|
|
6146
|
+
broadcast: () => this.broadcastReload()
|
|
6147
|
+
},
|
|
6148
|
+
source
|
|
6149
|
+
);
|
|
5710
6150
|
}
|
|
5711
|
-
/**
|
|
5712
|
-
* Handle info request - calls custom callback or broadcasts to components.
|
|
5713
|
-
*
|
|
5714
|
-
* When called from signal handlers, the Promise executes but return values
|
|
5715
|
-
* are not accessible due to Node.js signal handler constraints.
|
|
5716
|
-
*
|
|
5717
|
-
* @param source - Whether triggered from signal manager or manual trigger
|
|
5718
|
-
*/
|
|
5719
6151
|
async handleInfoRequest(source = "trigger") {
|
|
5720
|
-
this.
|
|
5721
|
-
|
|
5722
|
-
if (this.onInfoRequested) {
|
|
5723
|
-
const broadcastFn = () => this.broadcastInfo();
|
|
5724
|
-
const result = this.onInfoRequested(broadcastFn);
|
|
5725
|
-
if (isPromise(result)) {
|
|
5726
|
-
await result;
|
|
5727
|
-
}
|
|
5728
|
-
return {
|
|
6152
|
+
return this.handleSignalRequest(
|
|
6153
|
+
{
|
|
5729
6154
|
signal: "info",
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
6155
|
+
dispatchedLogLabel: "Info dispatched",
|
|
6156
|
+
emitSignal: () => this.lifecycleEvents.signalInfo(),
|
|
6157
|
+
customCallback: this.onInfoRequested,
|
|
6158
|
+
broadcast: () => this.broadcastInfo()
|
|
6159
|
+
},
|
|
6160
|
+
source
|
|
6161
|
+
);
|
|
5736
6162
|
}
|
|
5737
|
-
/**
|
|
5738
|
-
* Handle debug request - calls custom callback or broadcasts to components.
|
|
5739
|
-
*
|
|
5740
|
-
* When called from signal handlers, the Promise executes but return values
|
|
5741
|
-
* are not accessible due to Node.js signal handler constraints.
|
|
5742
|
-
*
|
|
5743
|
-
* @param source - Whether triggered from signal manager or manual trigger
|
|
5744
|
-
*/
|
|
5745
6163
|
async handleDebugRequest(source = "trigger") {
|
|
5746
|
-
this.
|
|
5747
|
-
|
|
5748
|
-
if (this.onDebugRequested) {
|
|
5749
|
-
const broadcastFn = () => this.broadcastDebug();
|
|
5750
|
-
const result = this.onDebugRequested(broadcastFn);
|
|
5751
|
-
if (isPromise(result)) {
|
|
5752
|
-
await result;
|
|
5753
|
-
}
|
|
5754
|
-
return {
|
|
6164
|
+
return this.handleSignalRequest(
|
|
6165
|
+
{
|
|
5755
6166
|
signal: "debug",
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
6167
|
+
dispatchedLogLabel: "Debug dispatched",
|
|
6168
|
+
emitSignal: () => this.lifecycleEvents.signalDebug(),
|
|
6169
|
+
customCallback: this.onDebugRequested,
|
|
6170
|
+
broadcast: () => this.broadcastDebug()
|
|
6171
|
+
},
|
|
6172
|
+
source
|
|
6173
|
+
);
|
|
5762
6174
|
}
|
|
5763
6175
|
/**
|
|
5764
|
-
*
|
|
5765
|
-
*
|
|
5766
|
-
*
|
|
6176
|
+
* Shared signal broadcast pipeline used by reload/info/debug.
|
|
6177
|
+
* Iterates running components, runs the picked handler with timeout, and
|
|
6178
|
+
* aggregates per-component results into a SignalBroadcastResult.
|
|
5767
6179
|
*/
|
|
5768
|
-
async
|
|
6180
|
+
async runSignalBroadcast(descriptor) {
|
|
5769
6181
|
const results = [];
|
|
5770
|
-
const
|
|
6182
|
+
const targets = this.components.filter(
|
|
5771
6183
|
(component) => this.runningComponents.has(component.getName())
|
|
5772
6184
|
);
|
|
5773
6185
|
if (this.isStarting) {
|
|
5774
|
-
this.logger.info(
|
|
5775
|
-
"Reload during startup: only reloading already-started components"
|
|
5776
|
-
);
|
|
6186
|
+
this.logger.info(descriptor.startupLog);
|
|
5777
6187
|
}
|
|
5778
|
-
for (const component of
|
|
6188
|
+
for (const component of targets) {
|
|
5779
6189
|
const name = component.getName();
|
|
5780
|
-
|
|
6190
|
+
const handler = descriptor.pickHandler(component);
|
|
6191
|
+
if (!handler) {
|
|
5781
6192
|
results.push({
|
|
5782
6193
|
name,
|
|
5783
6194
|
called: false,
|
|
@@ -5787,13 +6198,13 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
5787
6198
|
});
|
|
5788
6199
|
continue;
|
|
5789
6200
|
}
|
|
5790
|
-
|
|
6201
|
+
descriptor.emitStarted(name);
|
|
5791
6202
|
const timeoutMS = component.signalTimeoutMS;
|
|
5792
6203
|
let timeoutHandle;
|
|
5793
6204
|
const timeoutResult = { timedOut: true };
|
|
5794
6205
|
try {
|
|
5795
|
-
const
|
|
5796
|
-
const handlerPromise = isPromise(
|
|
6206
|
+
const handlerResult = handler();
|
|
6207
|
+
const handlerPromise = isPromise(handlerResult) ? handlerResult : Promise.resolve(handlerResult);
|
|
5797
6208
|
const outcome = timeoutMS > 0 ? await Promise.race([
|
|
5798
6209
|
handlerPromise,
|
|
5799
6210
|
new Promise((resolve) => {
|
|
@@ -5803,7 +6214,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
5803
6214
|
})
|
|
5804
6215
|
]) : await handlerPromise;
|
|
5805
6216
|
if (outcome === timeoutResult) {
|
|
5806
|
-
this.logger.entity(name).warn(
|
|
6217
|
+
this.logger.entity(name).warn(descriptor.timeoutLog, {
|
|
5807
6218
|
params: { timeoutMS }
|
|
5808
6219
|
});
|
|
5809
6220
|
Promise.resolve(handlerPromise).catch(() => {
|
|
@@ -5816,7 +6227,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
5816
6227
|
code: "timeout"
|
|
5817
6228
|
});
|
|
5818
6229
|
} else {
|
|
5819
|
-
|
|
6230
|
+
descriptor.emitCompleted(name);
|
|
5820
6231
|
results.push({
|
|
5821
6232
|
name,
|
|
5822
6233
|
called: true,
|
|
@@ -5827,10 +6238,10 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
5827
6238
|
}
|
|
5828
6239
|
} catch (error) {
|
|
5829
6240
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
5830
|
-
this.logger.entity(name).error(
|
|
6241
|
+
this.logger.entity(name).error(descriptor.errorLog, {
|
|
5831
6242
|
params: { error: err }
|
|
5832
6243
|
});
|
|
5833
|
-
|
|
6244
|
+
descriptor.emitFailed(name, err);
|
|
5834
6245
|
results.push({
|
|
5835
6246
|
name,
|
|
5836
6247
|
called: true,
|
|
@@ -5851,108 +6262,45 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
5851
6262
|
const isAllTimeout = calledResults.length > 0 && calledResults.every((result) => result.timedOut);
|
|
5852
6263
|
const code = hasError ? isAllError ? "error" : "partial_error" : hasTimeout ? isAllTimeout ? "timeout" : "partial_timeout" : "ok";
|
|
5853
6264
|
return {
|
|
5854
|
-
signal:
|
|
6265
|
+
signal: descriptor.signal,
|
|
5855
6266
|
results,
|
|
5856
6267
|
timedOut: hasTimeout,
|
|
5857
6268
|
code
|
|
5858
6269
|
};
|
|
5859
6270
|
}
|
|
6271
|
+
/**
|
|
6272
|
+
* Broadcast reload signal to all running components.
|
|
6273
|
+
* Calls onReload() on components that implement it.
|
|
6274
|
+
* Continues on errors - collects all results.
|
|
6275
|
+
*/
|
|
6276
|
+
async broadcastReload() {
|
|
6277
|
+
return this.runSignalBroadcast({
|
|
6278
|
+
signal: "reload",
|
|
6279
|
+
pickHandler: (component) => component.onReload?.bind(component),
|
|
6280
|
+
startupLog: "Reload during startup: only reloading already-started components",
|
|
6281
|
+
timeoutLog: "Reload handler timed out",
|
|
6282
|
+
errorLog: "Reload failed: {{error.message}}",
|
|
6283
|
+
emitStarted: (name) => this.lifecycleEvents.componentReloadStarted(name),
|
|
6284
|
+
emitCompleted: (name) => this.lifecycleEvents.componentReloadCompleted(name),
|
|
6285
|
+
emitFailed: (name, error) => this.lifecycleEvents.componentReloadFailed(name, error)
|
|
6286
|
+
});
|
|
6287
|
+
}
|
|
5860
6288
|
/**
|
|
5861
6289
|
* Broadcast info signal to all running components.
|
|
5862
6290
|
* Calls onInfo() on components that implement it.
|
|
5863
6291
|
* Continues on errors - collects all results.
|
|
5864
6292
|
*/
|
|
5865
6293
|
async broadcastInfo() {
|
|
5866
|
-
|
|
5867
|
-
const componentsToNotify = this.components.filter(
|
|
5868
|
-
(component) => this.runningComponents.has(component.getName())
|
|
5869
|
-
);
|
|
5870
|
-
if (this.isStarting) {
|
|
5871
|
-
this.logger.info(
|
|
5872
|
-
"Info during startup: only notifying already-started components"
|
|
5873
|
-
);
|
|
5874
|
-
}
|
|
5875
|
-
for (const component of componentsToNotify) {
|
|
5876
|
-
const name = component.getName();
|
|
5877
|
-
if (!component.onInfo) {
|
|
5878
|
-
results.push({
|
|
5879
|
-
name,
|
|
5880
|
-
called: false,
|
|
5881
|
-
error: null,
|
|
5882
|
-
timedOut: false,
|
|
5883
|
-
code: "no_handler"
|
|
5884
|
-
});
|
|
5885
|
-
continue;
|
|
5886
|
-
}
|
|
5887
|
-
this.lifecycleEvents.componentInfoStarted(name);
|
|
5888
|
-
const timeoutMS = component.signalTimeoutMS;
|
|
5889
|
-
let timeoutHandle;
|
|
5890
|
-
const timeoutResult = { timedOut: true };
|
|
5891
|
-
try {
|
|
5892
|
-
const result = component.onInfo();
|
|
5893
|
-
const handlerPromise = isPromise(result) ? result : Promise.resolve(result);
|
|
5894
|
-
const outcome = timeoutMS > 0 ? await Promise.race([
|
|
5895
|
-
handlerPromise,
|
|
5896
|
-
new Promise((resolve) => {
|
|
5897
|
-
timeoutHandle = setTimeout(() => {
|
|
5898
|
-
resolve(timeoutResult);
|
|
5899
|
-
}, timeoutMS);
|
|
5900
|
-
})
|
|
5901
|
-
]) : await handlerPromise;
|
|
5902
|
-
if (outcome === timeoutResult) {
|
|
5903
|
-
this.logger.entity(name).warn("Info handler timed out", {
|
|
5904
|
-
params: { timeoutMS }
|
|
5905
|
-
});
|
|
5906
|
-
Promise.resolve(handlerPromise).catch(() => {
|
|
5907
|
-
});
|
|
5908
|
-
results.push({
|
|
5909
|
-
name,
|
|
5910
|
-
called: true,
|
|
5911
|
-
error: null,
|
|
5912
|
-
timedOut: true,
|
|
5913
|
-
code: "timeout"
|
|
5914
|
-
});
|
|
5915
|
-
} else {
|
|
5916
|
-
this.lifecycleEvents.componentInfoCompleted(name);
|
|
5917
|
-
results.push({
|
|
5918
|
-
name,
|
|
5919
|
-
called: true,
|
|
5920
|
-
error: null,
|
|
5921
|
-
timedOut: false,
|
|
5922
|
-
code: "called"
|
|
5923
|
-
});
|
|
5924
|
-
}
|
|
5925
|
-
} catch (error) {
|
|
5926
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
5927
|
-
this.logger.entity(name).error("Info handler failed: {{error.message}}", {
|
|
5928
|
-
params: { error: err }
|
|
5929
|
-
});
|
|
5930
|
-
this.lifecycleEvents.componentInfoFailed(name, err);
|
|
5931
|
-
results.push({
|
|
5932
|
-
name,
|
|
5933
|
-
called: true,
|
|
5934
|
-
error: err,
|
|
5935
|
-
timedOut: false,
|
|
5936
|
-
code: "error"
|
|
5937
|
-
});
|
|
5938
|
-
} finally {
|
|
5939
|
-
if (timeoutHandle) {
|
|
5940
|
-
clearTimeout(timeoutHandle);
|
|
5941
|
-
}
|
|
5942
|
-
}
|
|
5943
|
-
}
|
|
5944
|
-
const calledResults = results.filter((result) => result.called);
|
|
5945
|
-
const hasError = calledResults.some((result) => result.error);
|
|
5946
|
-
const isAllError = calledResults.length > 0 && calledResults.every((result) => result.error);
|
|
5947
|
-
const hasTimeout = calledResults.some((result) => result.timedOut);
|
|
5948
|
-
const isAllTimeout = calledResults.length > 0 && calledResults.every((result) => result.timedOut);
|
|
5949
|
-
const code = hasError ? isAllError ? "error" : "partial_error" : hasTimeout ? isAllTimeout ? "timeout" : "partial_timeout" : "ok";
|
|
5950
|
-
return {
|
|
6294
|
+
return this.runSignalBroadcast({
|
|
5951
6295
|
signal: "info",
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
6296
|
+
pickHandler: (component) => component.onInfo?.bind(component),
|
|
6297
|
+
startupLog: "Info during startup: only notifying already-started components",
|
|
6298
|
+
timeoutLog: "Info handler timed out",
|
|
6299
|
+
errorLog: "Info handler failed: {{error.message}}",
|
|
6300
|
+
emitStarted: (name) => this.lifecycleEvents.componentInfoStarted(name),
|
|
6301
|
+
emitCompleted: (name) => this.lifecycleEvents.componentInfoCompleted(name),
|
|
6302
|
+
emitFailed: (name, error) => this.lifecycleEvents.componentInfoFailed(name, error)
|
|
6303
|
+
});
|
|
5956
6304
|
}
|
|
5957
6305
|
/**
|
|
5958
6306
|
* Broadcast debug signal to all running components.
|
|
@@ -5960,96 +6308,16 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
5960
6308
|
* Continues on errors - collects all results.
|
|
5961
6309
|
*/
|
|
5962
6310
|
async broadcastDebug() {
|
|
5963
|
-
|
|
5964
|
-
const componentsToNotify = this.components.filter(
|
|
5965
|
-
(component) => this.runningComponents.has(component.getName())
|
|
5966
|
-
);
|
|
5967
|
-
if (this.isStarting) {
|
|
5968
|
-
this.logger.info(
|
|
5969
|
-
"Debug during startup: only notifying already-started components"
|
|
5970
|
-
);
|
|
5971
|
-
}
|
|
5972
|
-
for (const component of componentsToNotify) {
|
|
5973
|
-
const name = component.getName();
|
|
5974
|
-
if (!component.onDebug) {
|
|
5975
|
-
results.push({
|
|
5976
|
-
name,
|
|
5977
|
-
called: false,
|
|
5978
|
-
error: null,
|
|
5979
|
-
timedOut: false,
|
|
5980
|
-
code: "no_handler"
|
|
5981
|
-
});
|
|
5982
|
-
continue;
|
|
5983
|
-
}
|
|
5984
|
-
this.lifecycleEvents.componentDebugStarted(name);
|
|
5985
|
-
const timeoutMS = component.signalTimeoutMS;
|
|
5986
|
-
let timeoutHandle;
|
|
5987
|
-
const timeoutResult = { timedOut: true };
|
|
5988
|
-
try {
|
|
5989
|
-
const result = component.onDebug();
|
|
5990
|
-
const handlerPromise = isPromise(result) ? result : Promise.resolve(result);
|
|
5991
|
-
const outcome = timeoutMS > 0 ? await Promise.race([
|
|
5992
|
-
handlerPromise,
|
|
5993
|
-
new Promise((resolve) => {
|
|
5994
|
-
timeoutHandle = setTimeout(() => {
|
|
5995
|
-
resolve(timeoutResult);
|
|
5996
|
-
}, timeoutMS);
|
|
5997
|
-
})
|
|
5998
|
-
]) : await handlerPromise;
|
|
5999
|
-
if (outcome === timeoutResult) {
|
|
6000
|
-
this.logger.entity(name).warn("Debug handler timed out", {
|
|
6001
|
-
params: { timeoutMS }
|
|
6002
|
-
});
|
|
6003
|
-
Promise.resolve(handlerPromise).catch(() => {
|
|
6004
|
-
});
|
|
6005
|
-
results.push({
|
|
6006
|
-
name,
|
|
6007
|
-
called: true,
|
|
6008
|
-
error: null,
|
|
6009
|
-
timedOut: true,
|
|
6010
|
-
code: "timeout"
|
|
6011
|
-
});
|
|
6012
|
-
} else {
|
|
6013
|
-
this.lifecycleEvents.componentDebugCompleted(name);
|
|
6014
|
-
results.push({
|
|
6015
|
-
name,
|
|
6016
|
-
called: true,
|
|
6017
|
-
error: null,
|
|
6018
|
-
timedOut: false,
|
|
6019
|
-
code: "called"
|
|
6020
|
-
});
|
|
6021
|
-
}
|
|
6022
|
-
} catch (error) {
|
|
6023
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
6024
|
-
this.logger.entity(name).error("Debug handler failed: {{error.message}}", {
|
|
6025
|
-
params: { error: err }
|
|
6026
|
-
});
|
|
6027
|
-
this.lifecycleEvents.componentDebugFailed(name, err);
|
|
6028
|
-
results.push({
|
|
6029
|
-
name,
|
|
6030
|
-
called: true,
|
|
6031
|
-
error: err,
|
|
6032
|
-
timedOut: false,
|
|
6033
|
-
code: "error"
|
|
6034
|
-
});
|
|
6035
|
-
} finally {
|
|
6036
|
-
if (timeoutHandle) {
|
|
6037
|
-
clearTimeout(timeoutHandle);
|
|
6038
|
-
}
|
|
6039
|
-
}
|
|
6040
|
-
}
|
|
6041
|
-
const calledResults = results.filter((result) => result.called);
|
|
6042
|
-
const hasError = calledResults.some((result) => result.error);
|
|
6043
|
-
const isAllError = calledResults.length > 0 && calledResults.every((result) => result.error);
|
|
6044
|
-
const hasTimeout = calledResults.some((result) => result.timedOut);
|
|
6045
|
-
const isAllTimeout = calledResults.length > 0 && calledResults.every((result) => result.timedOut);
|
|
6046
|
-
const code = hasError ? isAllError ? "error" : "partial_error" : hasTimeout ? isAllTimeout ? "timeout" : "partial_timeout" : "ok";
|
|
6047
|
-
return {
|
|
6311
|
+
return this.runSignalBroadcast({
|
|
6048
6312
|
signal: "debug",
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6313
|
+
pickHandler: (component) => component.onDebug?.bind(component),
|
|
6314
|
+
startupLog: "Debug during startup: only notifying already-started components",
|
|
6315
|
+
timeoutLog: "Debug handler timed out",
|
|
6316
|
+
errorLog: "Debug handler failed: {{error.message}}",
|
|
6317
|
+
emitStarted: (name) => this.lifecycleEvents.componentDebugStarted(name),
|
|
6318
|
+
emitCompleted: (name) => this.lifecycleEvents.componentDebugCompleted(name),
|
|
6319
|
+
emitFailed: (name, error) => this.lifecycleEvents.componentDebugFailed(name, error)
|
|
6320
|
+
});
|
|
6053
6321
|
}
|
|
6054
6322
|
};
|
|
6055
6323
|
|
|
@@ -6075,6 +6343,12 @@ var BaseComponent = class {
|
|
|
6075
6343
|
name;
|
|
6076
6344
|
/** Reference to component-scoped lifecycle (set by manager when registered) */
|
|
6077
6345
|
lifecycle;
|
|
6346
|
+
/** @internal Set by LifecycleManager while the component is running. */
|
|
6347
|
+
_unexpectedStopHandler;
|
|
6348
|
+
/** @internal Incremented whenever the unexpected-stop handler is re-armed or cleared. */
|
|
6349
|
+
_unexpectedStopGeneration = 0;
|
|
6350
|
+
/** @internal Flag indicating whether this component is currently registered with a LifecycleManager */
|
|
6351
|
+
_isRegistered = false;
|
|
6078
6352
|
/**
|
|
6079
6353
|
* Create a new component
|
|
6080
6354
|
*
|
|
@@ -6109,6 +6383,37 @@ var BaseComponent = class {
|
|
|
6109
6383
|
// Default if undefined/null/non-finite
|
|
6110
6384
|
);
|
|
6111
6385
|
}
|
|
6386
|
+
/** @internal Called by LifecycleManager after a successful start. */
|
|
6387
|
+
_setUnexpectedStopHandler(handler) {
|
|
6388
|
+
this._unexpectedStopGeneration += 1;
|
|
6389
|
+
this._unexpectedStopHandler = handler;
|
|
6390
|
+
const generation = this._unexpectedStopGeneration;
|
|
6391
|
+
this.reportUnexpectedStop = (error) => {
|
|
6392
|
+
if (this._unexpectedStopGeneration !== generation) {
|
|
6393
|
+
return false;
|
|
6394
|
+
}
|
|
6395
|
+
return this._unexpectedStopHandler?.(error) ?? false;
|
|
6396
|
+
};
|
|
6397
|
+
}
|
|
6398
|
+
/** @internal Called by LifecycleManager when stop begins or component is unregistered. */
|
|
6399
|
+
_clearUnexpectedStopHandler() {
|
|
6400
|
+
this._unexpectedStopGeneration += 1;
|
|
6401
|
+
this._unexpectedStopHandler = void 0;
|
|
6402
|
+
this.reportUnexpectedStop = () => false;
|
|
6403
|
+
}
|
|
6404
|
+
/** @internal Called by LifecycleManager when registering the component. */
|
|
6405
|
+
_markRegistered() {
|
|
6406
|
+
this._isRegistered = true;
|
|
6407
|
+
}
|
|
6408
|
+
/** @internal Called by LifecycleManager when unregistering the component. */
|
|
6409
|
+
_markUnregistered() {
|
|
6410
|
+
this._isRegistered = false;
|
|
6411
|
+
this.lifecycle = void 0;
|
|
6412
|
+
}
|
|
6413
|
+
/** @internal Check if the component is registered with any LifecycleManager. */
|
|
6414
|
+
_isRegisteredWithManager() {
|
|
6415
|
+
return this._isRegistered;
|
|
6416
|
+
}
|
|
6112
6417
|
/**
|
|
6113
6418
|
* Get component name
|
|
6114
6419
|
*/
|
|
@@ -6127,6 +6432,38 @@ var BaseComponent = class {
|
|
|
6127
6432
|
isOptional() {
|
|
6128
6433
|
return this.optional;
|
|
6129
6434
|
}
|
|
6435
|
+
/**
|
|
6436
|
+
* Run-scoped unexpected-stop callback. Rebound by LifecycleManager on each
|
|
6437
|
+
* successful start so captured references from older runs go stale.
|
|
6438
|
+
*/
|
|
6439
|
+
reportUnexpectedStop = () => false;
|
|
6440
|
+
/**
|
|
6441
|
+
* Get this component's own status from the manager's perspective.
|
|
6442
|
+
*
|
|
6443
|
+
* Equivalent to `this.lifecycle.getComponentStatus(this.getName())` but without
|
|
6444
|
+
* needing to pass the name. Returns `undefined` if the component is not registered.
|
|
6445
|
+
*
|
|
6446
|
+
* Check `status?.state === 'running'` to test whether the component is currently running.
|
|
6447
|
+
*/
|
|
6448
|
+
getSelfStatus() {
|
|
6449
|
+
return this.lifecycle?.getComponentStatus(this.name);
|
|
6450
|
+
}
|
|
6451
|
+
/**
|
|
6452
|
+
* Capture a run-scoped unexpected-stop reporter for async listeners created during start().
|
|
6453
|
+
*
|
|
6454
|
+
* Unlike calling `this.reportUnexpectedStop()` later, the returned callback becomes a no-op
|
|
6455
|
+
* once the component is stopped, unregistered, or restarted. This prevents stale listeners
|
|
6456
|
+
* from a previous run from stopping a newer run of the same component instance.
|
|
6457
|
+
*/
|
|
6458
|
+
getUnexpectedStopReporter() {
|
|
6459
|
+
const generation = this._unexpectedStopGeneration;
|
|
6460
|
+
return (error) => {
|
|
6461
|
+
if (this._unexpectedStopGeneration !== generation) {
|
|
6462
|
+
return false;
|
|
6463
|
+
}
|
|
6464
|
+
return this._unexpectedStopHandler?.(error) ?? false;
|
|
6465
|
+
};
|
|
6466
|
+
}
|
|
6130
6467
|
};
|
|
6131
6468
|
export {
|
|
6132
6469
|
BaseComponent,
|