lifecycleion 0.0.13 → 0.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/lib/lifecycle-manager/index.cjs +642 -327
- package/dist/lib/lifecycle-manager/index.cjs.map +1 -1
- package/dist/lib/lifecycle-manager/index.d.cts +98 -23
- package/dist/lib/lifecycle-manager/index.d.ts +98 -23
- package/dist/lib/lifecycle-manager/index.js +642 -327
- package/dist/lib/lifecycle-manager/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -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,25 @@ 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
|
+
|
|
1402
1431
|
// src/lib/process-signal-manager.ts
|
|
1403
1432
|
import { ulid } from "ulid";
|
|
1404
1433
|
import readline from "readline";
|
|
@@ -1943,8 +1972,15 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
1943
1972
|
componentTimestamps = /* @__PURE__ */ new Map();
|
|
1944
1973
|
componentErrors = /* @__PURE__ */ new Map();
|
|
1945
1974
|
componentStartAttemptTokens = /* @__PURE__ */ new Map();
|
|
1975
|
+
// Use per-stop ULIDs instead of incrementing counters because a stalled
|
|
1976
|
+
// component can be unregistered and replaced by a same-name instance before
|
|
1977
|
+
// the old floating stop promise settles.
|
|
1978
|
+
componentStopAttemptTokens = /* @__PURE__ */ new Map();
|
|
1979
|
+
pendingForceStopWaiters = /* @__PURE__ */ new Map();
|
|
1980
|
+
unexpectedStopsDuringStartup = /* @__PURE__ */ new Map();
|
|
1946
1981
|
// State flags
|
|
1947
1982
|
isStarting = false;
|
|
1983
|
+
autoAttachedSignalsDuringStartup = false;
|
|
1948
1984
|
isStarted = false;
|
|
1949
1985
|
isShuttingDown = false;
|
|
1950
1986
|
// Unique token used to detect shutdowns that happened during async start().
|
|
@@ -2093,7 +2129,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2093
2129
|
*/
|
|
2094
2130
|
async unregisterComponent(name, options) {
|
|
2095
2131
|
if (this.isStarting || this.isShuttingDown) {
|
|
2096
|
-
this.logger.entity(name).warn(
|
|
2132
|
+
this.logger.entity(name).warn(LIFECYCLE_MANAGER_MESSAGE_BULK_OPERATION_IN_PROGRESS, {
|
|
2097
2133
|
params: {
|
|
2098
2134
|
isStarting: this.isStarting,
|
|
2099
2135
|
isShuttingDown: this.isShuttingDown
|
|
@@ -2102,7 +2138,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2102
2138
|
return {
|
|
2103
2139
|
success: false,
|
|
2104
2140
|
componentName: name,
|
|
2105
|
-
reason:
|
|
2141
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_BULK_OPERATION_IN_PROGRESS,
|
|
2106
2142
|
code: "bulk_operation_in_progress",
|
|
2107
2143
|
wasStopped: false,
|
|
2108
2144
|
wasRegistered: this.hasComponent(name)
|
|
@@ -2110,11 +2146,11 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2110
2146
|
}
|
|
2111
2147
|
const component = this.getComponent(name);
|
|
2112
2148
|
if (!component) {
|
|
2113
|
-
this.logger.entity(name).warn(
|
|
2149
|
+
this.logger.entity(name).warn(LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_FOUND);
|
|
2114
2150
|
return {
|
|
2115
2151
|
success: false,
|
|
2116
2152
|
componentName: name,
|
|
2117
|
-
reason:
|
|
2153
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_FOUND,
|
|
2118
2154
|
code: "component_not_found",
|
|
2119
2155
|
wasStopped: false,
|
|
2120
2156
|
wasRegistered: false
|
|
@@ -2127,7 +2163,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2127
2163
|
return {
|
|
2128
2164
|
success: false,
|
|
2129
2165
|
componentName: name,
|
|
2130
|
-
reason:
|
|
2166
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_STALLED,
|
|
2131
2167
|
code: "stop_failed",
|
|
2132
2168
|
stopFailureReason: "stalled",
|
|
2133
2169
|
wasStopped: false,
|
|
@@ -2179,10 +2215,13 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2179
2215
|
wasStopped = true;
|
|
2180
2216
|
}
|
|
2181
2217
|
this.components = this.components.filter((c) => c.getName() !== name);
|
|
2218
|
+
component._clearUnexpectedStopHandler();
|
|
2182
2219
|
this.componentStates.delete(name);
|
|
2183
2220
|
this.componentTimestamps.delete(name);
|
|
2184
2221
|
this.componentErrors.delete(name);
|
|
2185
2222
|
this.componentStartAttemptTokens.delete(name);
|
|
2223
|
+
this.componentStopAttemptTokens.delete(name);
|
|
2224
|
+
this.pendingForceStopWaiters.delete(name);
|
|
2186
2225
|
this.stalledComponents.delete(name);
|
|
2187
2226
|
this.runningComponents.delete(name);
|
|
2188
2227
|
this.updateStartedFlag();
|
|
@@ -2529,7 +2568,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2529
2568
|
startedComponents: [],
|
|
2530
2569
|
failedOptionalComponents: [],
|
|
2531
2570
|
skippedDueToDependency: [],
|
|
2532
|
-
reason:
|
|
2571
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_SHUTDOWN_IN_PROGRESS,
|
|
2533
2572
|
code: "shutdown_in_progress",
|
|
2534
2573
|
durationMS: Date.now() - startTime
|
|
2535
2574
|
};
|
|
@@ -2589,6 +2628,8 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2589
2628
|
};
|
|
2590
2629
|
}
|
|
2591
2630
|
this.isStarting = true;
|
|
2631
|
+
this.autoAttachedSignalsDuringStartup = false;
|
|
2632
|
+
this.unexpectedStopsDuringStartup.clear();
|
|
2592
2633
|
this.resetRepeatedShutdownRequestState();
|
|
2593
2634
|
this.shutdownMethod = null;
|
|
2594
2635
|
this.lastShutdownResult = null;
|
|
@@ -2721,13 +2762,49 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2721
2762
|
error: result.error,
|
|
2722
2763
|
durationMS: Date.now() - startTime
|
|
2723
2764
|
};
|
|
2765
|
+
} else if (result.code === "component_unexpected_stop") {
|
|
2766
|
+
this.unexpectedStopsDuringStartup.delete(name);
|
|
2767
|
+
const error = result.error || new Error(
|
|
2768
|
+
result.reason || `Component "${name}" stopped unexpectedly`
|
|
2769
|
+
);
|
|
2770
|
+
if (component.isOptional()) {
|
|
2771
|
+
if (!failedOptionalComponents.some((entry) => entry.name === name)) {
|
|
2772
|
+
failedOptionalComponents.push({ name, error });
|
|
2773
|
+
}
|
|
2774
|
+
this.logger.entity(name).warn(
|
|
2775
|
+
LIFECYCLE_MANAGER_LOG_OPTIONAL_COMPONENT_UNEXPECTED_STOP_DURING_STARTUP,
|
|
2776
|
+
{
|
|
2777
|
+
params: { error }
|
|
2778
|
+
}
|
|
2779
|
+
);
|
|
2780
|
+
} else {
|
|
2781
|
+
this.logger.entity(name).error(
|
|
2782
|
+
LIFECYCLE_MANAGER_LOG_REQUIRED_COMPONENT_UNEXPECTED_STOP_DURING_STARTUP,
|
|
2783
|
+
{
|
|
2784
|
+
params: { error }
|
|
2785
|
+
}
|
|
2786
|
+
);
|
|
2787
|
+
await this.rollbackStartup(startedComponents);
|
|
2788
|
+
return {
|
|
2789
|
+
success: false,
|
|
2790
|
+
startedComponents: [],
|
|
2791
|
+
failedOptionalComponents,
|
|
2792
|
+
skippedDueToDependency: Array.from(skippedDueToDependency),
|
|
2793
|
+
reason: error.message,
|
|
2794
|
+
code: "component_unexpected_stop",
|
|
2795
|
+
error,
|
|
2796
|
+
durationMS: Date.now() - startTime
|
|
2797
|
+
};
|
|
2798
|
+
}
|
|
2724
2799
|
} else {
|
|
2725
2800
|
if (component.isOptional()) {
|
|
2726
2801
|
this.logger.entity(name).warn(
|
|
2727
2802
|
"Optional component failed to start, continuing: {{error.message}}",
|
|
2728
2803
|
{
|
|
2729
2804
|
params: {
|
|
2730
|
-
error: result.error || new Error(
|
|
2805
|
+
error: result.error || new Error(
|
|
2806
|
+
result.reason || LIFECYCLE_MANAGER_MESSAGE_UNKNOWN_ERROR
|
|
2807
|
+
)
|
|
2731
2808
|
}
|
|
2732
2809
|
}
|
|
2733
2810
|
);
|
|
@@ -2741,14 +2818,18 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2741
2818
|
}
|
|
2742
2819
|
failedOptionalComponents.push({
|
|
2743
2820
|
name,
|
|
2744
|
-
error: result.error || new Error(
|
|
2821
|
+
error: result.error || new Error(
|
|
2822
|
+
result.reason || LIFECYCLE_MANAGER_MESSAGE_UNKNOWN_ERROR
|
|
2823
|
+
)
|
|
2745
2824
|
});
|
|
2746
2825
|
} else {
|
|
2747
2826
|
this.logger.entity(name).error(
|
|
2748
2827
|
"Required component failed to start, rolling back: {{error.message}}",
|
|
2749
2828
|
{
|
|
2750
2829
|
params: {
|
|
2751
|
-
error: result.error || new Error(
|
|
2830
|
+
error: result.error || new Error(
|
|
2831
|
+
result.reason || LIFECYCLE_MANAGER_MESSAGE_UNKNOWN_ERROR
|
|
2832
|
+
)
|
|
2752
2833
|
}
|
|
2753
2834
|
}
|
|
2754
2835
|
);
|
|
@@ -2765,6 +2846,25 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2765
2846
|
};
|
|
2766
2847
|
}
|
|
2767
2848
|
}
|
|
2849
|
+
const unexpectedStopResult2 = this.consumeUnexpectedStopsDuringStartup(
|
|
2850
|
+
startedComponents,
|
|
2851
|
+
failedOptionalComponents
|
|
2852
|
+
);
|
|
2853
|
+
startedComponents.splice(0, startedComponents.length);
|
|
2854
|
+
startedComponents.push(...unexpectedStopResult2.startedComponents);
|
|
2855
|
+
if (unexpectedStopResult2.requiredFailure) {
|
|
2856
|
+
await this.rollbackStartup(startedComponents);
|
|
2857
|
+
return {
|
|
2858
|
+
success: false,
|
|
2859
|
+
startedComponents: [],
|
|
2860
|
+
failedOptionalComponents,
|
|
2861
|
+
skippedDueToDependency: Array.from(skippedDueToDependency),
|
|
2862
|
+
reason: unexpectedStopResult2.requiredFailure.error.message,
|
|
2863
|
+
code: "component_unexpected_stop",
|
|
2864
|
+
error: unexpectedStopResult2.requiredFailure.error,
|
|
2865
|
+
durationMS: Date.now() - startTime
|
|
2866
|
+
};
|
|
2867
|
+
}
|
|
2768
2868
|
}
|
|
2769
2869
|
if (hasTimedOut) {
|
|
2770
2870
|
const durationMS2 = Date.now() - startTime;
|
|
@@ -2788,6 +2888,25 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2788
2888
|
code: "startup_timeout"
|
|
2789
2889
|
};
|
|
2790
2890
|
}
|
|
2891
|
+
const unexpectedStopResult = this.consumeUnexpectedStopsDuringStartup(
|
|
2892
|
+
startedComponents,
|
|
2893
|
+
failedOptionalComponents
|
|
2894
|
+
);
|
|
2895
|
+
startedComponents.splice(0, startedComponents.length);
|
|
2896
|
+
startedComponents.push(...unexpectedStopResult.startedComponents);
|
|
2897
|
+
if (unexpectedStopResult.requiredFailure) {
|
|
2898
|
+
await this.rollbackStartup(startedComponents);
|
|
2899
|
+
return {
|
|
2900
|
+
success: false,
|
|
2901
|
+
startedComponents: [],
|
|
2902
|
+
failedOptionalComponents,
|
|
2903
|
+
skippedDueToDependency: Array.from(skippedDueToDependency),
|
|
2904
|
+
reason: unexpectedStopResult.requiredFailure.error.message,
|
|
2905
|
+
code: "component_unexpected_stop",
|
|
2906
|
+
error: unexpectedStopResult.requiredFailure.error,
|
|
2907
|
+
durationMS: Date.now() - startTime
|
|
2908
|
+
};
|
|
2909
|
+
}
|
|
2791
2910
|
this.updateStartedFlag();
|
|
2792
2911
|
const skippedComponentsArray = [
|
|
2793
2912
|
...Array.from(skippedDueToDependency),
|
|
@@ -2819,10 +2938,12 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2819
2938
|
if (timeoutHandle) {
|
|
2820
2939
|
clearTimeout(timeoutHandle);
|
|
2821
2940
|
}
|
|
2822
|
-
|
|
2941
|
+
this.isStarting = false;
|
|
2942
|
+
if (didAutoAttachSignalsForBulkStartup || this.autoAttachedSignalsDuringStartup) {
|
|
2823
2943
|
this.autoDetachSignalsIfIdle("failed bulk startup");
|
|
2824
2944
|
}
|
|
2825
|
-
this.
|
|
2945
|
+
this.autoAttachedSignalsDuringStartup = false;
|
|
2946
|
+
this.unexpectedStopsDuringStartup.clear();
|
|
2826
2947
|
}
|
|
2827
2948
|
}
|
|
2828
2949
|
/**
|
|
@@ -2886,7 +3007,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2886
3007
|
return {
|
|
2887
3008
|
success: false,
|
|
2888
3009
|
componentName: name,
|
|
2889
|
-
reason:
|
|
3010
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_BULK_STARTUP_IN_PROGRESS,
|
|
2890
3011
|
code: "startup_in_progress"
|
|
2891
3012
|
};
|
|
2892
3013
|
}
|
|
@@ -2897,7 +3018,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2897
3018
|
return {
|
|
2898
3019
|
success: false,
|
|
2899
3020
|
componentName: name,
|
|
2900
|
-
reason:
|
|
3021
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_SHUTDOWN_IN_PROGRESS,
|
|
2901
3022
|
code: "shutdown_in_progress"
|
|
2902
3023
|
};
|
|
2903
3024
|
}
|
|
@@ -2931,7 +3052,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2931
3052
|
return {
|
|
2932
3053
|
success: false,
|
|
2933
3054
|
componentName: name,
|
|
2934
|
-
reason: this.isStarting ?
|
|
3055
|
+
reason: this.isStarting ? LIFECYCLE_MANAGER_MESSAGE_BULK_STARTUP_IN_PROGRESS : LIFECYCLE_MANAGER_MESSAGE_SHUTDOWN_IN_PROGRESS,
|
|
2935
3056
|
code: this.isStarting ? "startup_in_progress" : "shutdown_in_progress"
|
|
2936
3057
|
};
|
|
2937
3058
|
}
|
|
@@ -3113,7 +3234,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3113
3234
|
if (this.isShuttingDown) {
|
|
3114
3235
|
if (isFirstExit && this.pendingLoggerExitResolve === null) {
|
|
3115
3236
|
this.logger.debug(
|
|
3116
|
-
|
|
3237
|
+
LIFECYCLE_MANAGER_LOG_LOGGER_EXIT_DURING_SHUTDOWN,
|
|
3117
3238
|
{
|
|
3118
3239
|
params: { exitCode }
|
|
3119
3240
|
}
|
|
@@ -3122,7 +3243,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3122
3243
|
this.pendingLoggerExitResolve = resolve;
|
|
3123
3244
|
});
|
|
3124
3245
|
}
|
|
3125
|
-
this.logger.debug(
|
|
3246
|
+
this.logger.debug(LIFECYCLE_MANAGER_LOG_LOGGER_EXIT_DURING_SHUTDOWN, {
|
|
3126
3247
|
params: { exitCode }
|
|
3127
3248
|
});
|
|
3128
3249
|
return { action: "wait" };
|
|
@@ -3212,7 +3333,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3212
3333
|
return {
|
|
3213
3334
|
name,
|
|
3214
3335
|
healthy: false,
|
|
3215
|
-
message:
|
|
3336
|
+
message: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_FOUND,
|
|
3216
3337
|
checkedAt: startTime,
|
|
3217
3338
|
durationMS: 0,
|
|
3218
3339
|
error: null,
|
|
@@ -3225,7 +3346,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3225
3346
|
return {
|
|
3226
3347
|
name,
|
|
3227
3348
|
healthy: false,
|
|
3228
|
-
message: isStalled ?
|
|
3349
|
+
message: isStalled ? LIFECYCLE_MANAGER_MESSAGE_COMPONENT_STALLED : LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_RUNNING,
|
|
3229
3350
|
checkedAt: startTime,
|
|
3230
3351
|
durationMS: Date.now() - startTime,
|
|
3231
3352
|
error: null,
|
|
@@ -3441,7 +3562,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3441
3562
|
result = component.onMessage(payload, from);
|
|
3442
3563
|
} catch (error) {
|
|
3443
3564
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
3444
|
-
this.logger.entity(componentName).error(
|
|
3565
|
+
this.logger.entity(componentName).error(LIFECYCLE_MANAGER_LOG_MESSAGE_HANDLER_FAILED, {
|
|
3445
3566
|
params: { error: err, from }
|
|
3446
3567
|
});
|
|
3447
3568
|
this.lifecycleEvents.componentMessageFailed(componentName, from, err, {
|
|
@@ -3501,7 +3622,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3501
3622
|
};
|
|
3502
3623
|
} catch (error) {
|
|
3503
3624
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
3504
|
-
this.logger.entity(componentName).error(
|
|
3625
|
+
this.logger.entity(componentName).error(LIFECYCLE_MANAGER_LOG_MESSAGE_HANDLER_FAILED, {
|
|
3505
3626
|
params: { error: err, from, timeoutMS }
|
|
3506
3627
|
});
|
|
3507
3628
|
this.lifecycleEvents.componentMessageFailed(componentName, from, err, {
|
|
@@ -3774,7 +3895,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3774
3895
|
this.lifecycleEvents.componentRegistrationRejected({
|
|
3775
3896
|
name: componentName,
|
|
3776
3897
|
reason: "shutdown_in_progress",
|
|
3777
|
-
message:
|
|
3898
|
+
message: LIFECYCLE_MANAGER_MESSAGE_REGISTER_SHUTDOWN_IN_PROGRESS,
|
|
3778
3899
|
registrationIndexBefore,
|
|
3779
3900
|
registrationIndexAfter: registrationIndexBefore,
|
|
3780
3901
|
requestedPosition: isInsertAction ? { position, targetComponentName } : void 0,
|
|
@@ -3786,7 +3907,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3786
3907
|
targetComponentName,
|
|
3787
3908
|
registrationIndexBefore,
|
|
3788
3909
|
code: "shutdown_in_progress",
|
|
3789
|
-
reason:
|
|
3910
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_REGISTER_SHUTDOWN_IN_PROGRESS,
|
|
3790
3911
|
targetFound: void 0
|
|
3791
3912
|
});
|
|
3792
3913
|
}
|
|
@@ -3797,7 +3918,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3797
3918
|
this.lifecycleEvents.componentRegistrationRejected({
|
|
3798
3919
|
name: componentName,
|
|
3799
3920
|
reason: "startup_in_progress",
|
|
3800
|
-
message:
|
|
3921
|
+
message: LIFECYCLE_MANAGER_MESSAGE_REGISTER_REQUIRED_DEPENDENCY_DURING_STARTUP,
|
|
3801
3922
|
registrationIndexBefore,
|
|
3802
3923
|
registrationIndexAfter: registrationIndexBefore,
|
|
3803
3924
|
requestedPosition: isInsertAction ? { position, targetComponentName } : void 0,
|
|
@@ -3809,7 +3930,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3809
3930
|
targetComponentName,
|
|
3810
3931
|
registrationIndexBefore,
|
|
3811
3932
|
code: "startup_in_progress",
|
|
3812
|
-
reason:
|
|
3933
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_REGISTER_REQUIRED_DEPENDENCY_DURING_STARTUP,
|
|
3813
3934
|
targetFound: void 0
|
|
3814
3935
|
});
|
|
3815
3936
|
}
|
|
@@ -3818,7 +3939,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3818
3939
|
this.lifecycleEvents.componentRegistrationRejected({
|
|
3819
3940
|
name: componentName,
|
|
3820
3941
|
reason: "duplicate_instance",
|
|
3821
|
-
message:
|
|
3942
|
+
message: LIFECYCLE_MANAGER_MESSAGE_DUPLICATE_COMPONENT_INSTANCE,
|
|
3822
3943
|
registrationIndexBefore,
|
|
3823
3944
|
registrationIndexAfter: registrationIndexBefore,
|
|
3824
3945
|
requestedPosition: isInsertAction ? { position, targetComponentName } : void 0,
|
|
@@ -3830,7 +3951,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3830
3951
|
targetComponentName,
|
|
3831
3952
|
registrationIndexBefore,
|
|
3832
3953
|
code: "duplicate_instance",
|
|
3833
|
-
reason:
|
|
3954
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_DUPLICATE_COMPONENT_INSTANCE,
|
|
3834
3955
|
targetFound: void 0
|
|
3835
3956
|
});
|
|
3836
3957
|
}
|
|
@@ -4144,8 +4265,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4144
4265
|
const runningComponentsToStop = shutdownOrder.filter(
|
|
4145
4266
|
(name) => this.isComponentRunning(name) || shouldRetryStalled && stalledComponentNames.has(name)
|
|
4146
4267
|
);
|
|
4147
|
-
const stoppedComponents =
|
|
4148
|
-
const stalledComponents = [];
|
|
4268
|
+
const stoppedComponents = /* @__PURE__ */ new Set();
|
|
4149
4269
|
let hasTimedOut = false;
|
|
4150
4270
|
let timeoutHandle;
|
|
4151
4271
|
try {
|
|
@@ -4176,34 +4296,37 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4176
4296
|
this.logger.entity(name).info("Stopping component");
|
|
4177
4297
|
const isRunning = this.isComponentRunning(name);
|
|
4178
4298
|
const isStalled = stalledComponentNames.has(name);
|
|
4299
|
+
const currentState = this.componentStates.get(name);
|
|
4300
|
+
if (currentState === "stopped") {
|
|
4301
|
+
stoppedComponents.add(name);
|
|
4302
|
+
continue;
|
|
4303
|
+
}
|
|
4179
4304
|
const result2 = isRunning ? await this.stopComponentInternal(name) : shouldRetryStalled && isStalled ? await this.retryStalledComponent(name) : isStalled ? {
|
|
4180
4305
|
success: false,
|
|
4181
4306
|
componentName: name,
|
|
4182
|
-
reason:
|
|
4307
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_STALLED,
|
|
4183
4308
|
code: "component_stalled",
|
|
4184
4309
|
status: this.getComponentStatus(name)
|
|
4185
4310
|
} : {
|
|
4186
4311
|
success: false,
|
|
4187
4312
|
componentName: name,
|
|
4188
|
-
reason:
|
|
4313
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_RUNNING,
|
|
4189
4314
|
code: "component_not_running",
|
|
4190
4315
|
status: this.getComponentStatus(name)
|
|
4191
4316
|
};
|
|
4192
4317
|
if (result2.success) {
|
|
4193
|
-
stoppedComponents.
|
|
4318
|
+
stoppedComponents.add(name);
|
|
4194
4319
|
} else {
|
|
4195
4320
|
this.logger.entity(name).error(
|
|
4196
4321
|
"Component failed to stop, continuing with others: {{error.message}}",
|
|
4197
4322
|
{
|
|
4198
4323
|
params: {
|
|
4199
|
-
error: result2.error || new Error(
|
|
4324
|
+
error: result2.error || new Error(
|
|
4325
|
+
result2.reason || LIFECYCLE_MANAGER_MESSAGE_UNKNOWN_ERROR
|
|
4326
|
+
)
|
|
4200
4327
|
}
|
|
4201
4328
|
}
|
|
4202
4329
|
);
|
|
4203
|
-
const stallInfo = this.stalledComponents.get(name);
|
|
4204
|
-
if (stallInfo) {
|
|
4205
|
-
stalledComponents.push(stallInfo);
|
|
4206
|
-
}
|
|
4207
4330
|
if (shouldHaltOnStall) {
|
|
4208
4331
|
this.logger.warn(
|
|
4209
4332
|
"Halting shutdown after stall (haltOnStall=true)",
|
|
@@ -4219,21 +4342,32 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4219
4342
|
} else {
|
|
4220
4343
|
await shutdownOperation();
|
|
4221
4344
|
}
|
|
4345
|
+
const finalStalledNames = /* @__PURE__ */ new Set();
|
|
4346
|
+
for (const name of runningComponentsToStop) {
|
|
4347
|
+
if (this.stalledComponents.has(name)) {
|
|
4348
|
+
finalStalledNames.add(name);
|
|
4349
|
+
}
|
|
4350
|
+
}
|
|
4222
4351
|
if (!shouldRetryStalled) {
|
|
4223
4352
|
for (const name of stalledComponentNames) {
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
stalledComponents.push(stallInfo);
|
|
4353
|
+
if (this.stalledComponents.has(name)) {
|
|
4354
|
+
finalStalledNames.add(name);
|
|
4227
4355
|
}
|
|
4228
4356
|
}
|
|
4229
4357
|
}
|
|
4358
|
+
for (const name of runningComponentsToStop) {
|
|
4359
|
+
if (!finalStalledNames.has(name) && this.componentStates.get(name) === "stopped") {
|
|
4360
|
+
stoppedComponents.add(name);
|
|
4361
|
+
}
|
|
4362
|
+
}
|
|
4363
|
+
const stalledComponents = Array.from(finalStalledNames).map((name) => this.stalledComponents.get(name)).filter((stallInfo) => !!stallInfo);
|
|
4230
4364
|
const durationMS = Date.now() - startTime;
|
|
4231
4365
|
const isSuccess = !hasTimedOut && stalledComponents.length === 0;
|
|
4232
4366
|
this.logger[isSuccess ? "success" : "warn"](
|
|
4233
4367
|
isSuccess ? "Shutdown completed successfully" : "Shutdown attempt completed with stalled components or timeout",
|
|
4234
4368
|
{
|
|
4235
4369
|
params: {
|
|
4236
|
-
stopped: stoppedComponents.
|
|
4370
|
+
stopped: stoppedComponents.size,
|
|
4237
4371
|
stalled: stalledComponents.length,
|
|
4238
4372
|
durationMS
|
|
4239
4373
|
}
|
|
@@ -4241,7 +4375,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4241
4375
|
);
|
|
4242
4376
|
const result = {
|
|
4243
4377
|
success: isSuccess,
|
|
4244
|
-
stoppedComponents,
|
|
4378
|
+
stoppedComponents: Array.from(stoppedComponents),
|
|
4245
4379
|
stalledComponents,
|
|
4246
4380
|
durationMS,
|
|
4247
4381
|
timedOut: hasTimedOut || void 0,
|
|
@@ -4292,7 +4426,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4292
4426
|
return {
|
|
4293
4427
|
success: false,
|
|
4294
4428
|
componentName: name,
|
|
4295
|
-
reason:
|
|
4429
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_FOUND,
|
|
4296
4430
|
code: "component_not_found"
|
|
4297
4431
|
};
|
|
4298
4432
|
}
|
|
@@ -4303,12 +4437,15 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4303
4437
|
return {
|
|
4304
4438
|
success: false,
|
|
4305
4439
|
componentName: name,
|
|
4306
|
-
reason:
|
|
4440
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_RUNNING,
|
|
4307
4441
|
code: "component_not_running",
|
|
4308
4442
|
status: this.getComponentStatus(name)
|
|
4309
4443
|
};
|
|
4310
4444
|
}
|
|
4311
4445
|
this.logger.entity(name).warn("Retrying stalled component shutdown (force phase)");
|
|
4446
|
+
if (component.onShutdownForce) {
|
|
4447
|
+
this.issueStopAttemptToken(name);
|
|
4448
|
+
}
|
|
4312
4449
|
return this.shutdownComponentForce(name, component, {
|
|
4313
4450
|
gracefulPhaseRan: false,
|
|
4314
4451
|
gracefulTimedOut: false,
|
|
@@ -4328,7 +4465,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4328
4465
|
return {
|
|
4329
4466
|
success: false,
|
|
4330
4467
|
componentName: name,
|
|
4331
|
-
reason:
|
|
4468
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_SHUTDOWN_IN_PROGRESS,
|
|
4332
4469
|
code: "shutdown_in_progress"
|
|
4333
4470
|
};
|
|
4334
4471
|
}
|
|
@@ -4340,7 +4477,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4340
4477
|
return {
|
|
4341
4478
|
success: false,
|
|
4342
4479
|
componentName: name,
|
|
4343
|
-
reason:
|
|
4480
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_BULK_STARTUP_IN_PROGRESS,
|
|
4344
4481
|
code: "startup_in_progress"
|
|
4345
4482
|
};
|
|
4346
4483
|
}
|
|
@@ -4350,7 +4487,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4350
4487
|
return {
|
|
4351
4488
|
success: false,
|
|
4352
4489
|
componentName: name,
|
|
4353
|
-
reason:
|
|
4490
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_FOUND,
|
|
4354
4491
|
code: "component_not_found"
|
|
4355
4492
|
};
|
|
4356
4493
|
}
|
|
@@ -4359,7 +4496,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4359
4496
|
return {
|
|
4360
4497
|
success: false,
|
|
4361
4498
|
componentName: name,
|
|
4362
|
-
reason:
|
|
4499
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_STALLED,
|
|
4363
4500
|
code: "component_stalled",
|
|
4364
4501
|
status: this.getComponentStatus(name)
|
|
4365
4502
|
};
|
|
@@ -4423,6 +4560,9 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4423
4560
|
const timeoutMS = component.startupTimeoutMS;
|
|
4424
4561
|
const startAttemptToken = ulid2();
|
|
4425
4562
|
this.componentStartAttemptTokens.set(name, startAttemptToken);
|
|
4563
|
+
component._setUnexpectedStopHandler(
|
|
4564
|
+
(error) => this.handleComponentUnexpectedStop(name, startAttemptToken, error)
|
|
4565
|
+
);
|
|
4426
4566
|
const shutdownTokenAtStart = this.shutdownToken;
|
|
4427
4567
|
const didAutoAttachSignalsForComponentStartup = this.attachSignalsBeforeStartup ? this.autoAttachSignals("component startup") : false;
|
|
4428
4568
|
let timeoutHandle;
|
|
@@ -4465,6 +4605,18 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4465
4605
|
} else {
|
|
4466
4606
|
await startPromise;
|
|
4467
4607
|
}
|
|
4608
|
+
if (this.componentStartAttemptTokens.get(name) === startAttemptToken && this.componentStates.get(name) === "stopped" && !this.runningComponents.has(name)) {
|
|
4609
|
+
component._clearUnexpectedStopHandler();
|
|
4610
|
+
const error = this.componentErrors.get(name) ?? new Error(`Component "${name}" stopped unexpectedly during startup`);
|
|
4611
|
+
return {
|
|
4612
|
+
success: false,
|
|
4613
|
+
componentName: name,
|
|
4614
|
+
reason: error.message,
|
|
4615
|
+
code: "component_unexpected_stop",
|
|
4616
|
+
error,
|
|
4617
|
+
status: this.getComponentStatus(name)
|
|
4618
|
+
};
|
|
4619
|
+
}
|
|
4468
4620
|
if (this.isShuttingDown || shutdownTokenAtStart !== this.shutdownToken) {
|
|
4469
4621
|
this.componentStates.set(name, "running");
|
|
4470
4622
|
this.runningComponents.add(name);
|
|
@@ -4492,6 +4644,9 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4492
4644
|
this.componentStates.set(name, "running");
|
|
4493
4645
|
this.runningComponents.add(name);
|
|
4494
4646
|
this.stalledComponents.delete(name);
|
|
4647
|
+
if (shouldForceStalled) {
|
|
4648
|
+
this.issueStopAttemptToken(name);
|
|
4649
|
+
}
|
|
4495
4650
|
this.updateStartedFlag();
|
|
4496
4651
|
if (this.attachSignalsOnStart && this.runningComponents.size === 1) {
|
|
4497
4652
|
this.autoAttachSignals("first component start");
|
|
@@ -4511,9 +4666,24 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4511
4666
|
status: this.getComponentStatus(name)
|
|
4512
4667
|
};
|
|
4513
4668
|
} catch (error) {
|
|
4669
|
+
component._clearUnexpectedStopHandler();
|
|
4514
4670
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
4671
|
+
const isStartupTimeout = err instanceof ComponentStartTimeoutError && err.additionalInfo.componentName === name;
|
|
4672
|
+
const unexpectedStopError = this.componentErrors.get(name);
|
|
4673
|
+
if (this.componentStartAttemptTokens.get(name) === startAttemptToken && this.componentStates.get(name) === "stopped" && !this.runningComponents.has(name) && (isStartupTimeout || unexpectedStopError instanceof Error)) {
|
|
4674
|
+
return {
|
|
4675
|
+
success: false,
|
|
4676
|
+
componentName: name,
|
|
4677
|
+
reason: unexpectedStopError?.message || `Component "${name}" stopped unexpectedly during startup`,
|
|
4678
|
+
code: "component_unexpected_stop",
|
|
4679
|
+
error: unexpectedStopError || new Error(
|
|
4680
|
+
`Component "${name}" stopped unexpectedly during startup`
|
|
4681
|
+
),
|
|
4682
|
+
status: this.getComponentStatus(name)
|
|
4683
|
+
};
|
|
4684
|
+
}
|
|
4515
4685
|
this.componentErrors.set(name, err);
|
|
4516
|
-
if (
|
|
4686
|
+
if (isStartupTimeout) {
|
|
4517
4687
|
this.componentStates.set(name, "starting-timed-out");
|
|
4518
4688
|
this.logger.entity(name).error("Component startup timed out: {{error.message}}", {
|
|
4519
4689
|
params: { error: err }
|
|
@@ -4558,7 +4728,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4558
4728
|
return {
|
|
4559
4729
|
success: false,
|
|
4560
4730
|
componentName: name,
|
|
4561
|
-
reason:
|
|
4731
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_FOUND,
|
|
4562
4732
|
code: "component_not_found"
|
|
4563
4733
|
};
|
|
4564
4734
|
}
|
|
@@ -4566,7 +4736,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4566
4736
|
return {
|
|
4567
4737
|
success: false,
|
|
4568
4738
|
componentName: name,
|
|
4569
|
-
reason:
|
|
4739
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_STALLED,
|
|
4570
4740
|
code: "component_stalled",
|
|
4571
4741
|
status: this.getComponentStatus(name)
|
|
4572
4742
|
};
|
|
@@ -4575,7 +4745,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4575
4745
|
return {
|
|
4576
4746
|
success: false,
|
|
4577
4747
|
componentName: name,
|
|
4578
|
-
reason:
|
|
4748
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_COMPONENT_NOT_RUNNING,
|
|
4579
4749
|
code: "component_not_running",
|
|
4580
4750
|
status: this.getComponentStatus(name)
|
|
4581
4751
|
};
|
|
@@ -4591,6 +4761,10 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4591
4761
|
};
|
|
4592
4762
|
}
|
|
4593
4763
|
if (options?.forceImmediate) {
|
|
4764
|
+
if (component.onShutdownForce) {
|
|
4765
|
+
this.issueStopAttemptToken(name);
|
|
4766
|
+
}
|
|
4767
|
+
component._clearUnexpectedStopHandler();
|
|
4594
4768
|
return this.shutdownComponentForce(name, component, {
|
|
4595
4769
|
gracefulPhaseRan: false,
|
|
4596
4770
|
gracefulTimedOut: false,
|
|
@@ -4727,9 +4901,11 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4727
4901
|
* Calls stop() with timeout
|
|
4728
4902
|
*/
|
|
4729
4903
|
async shutdownComponentGraceful(name, component, options) {
|
|
4904
|
+
component._clearUnexpectedStopHandler();
|
|
4730
4905
|
this.componentStates.set(name, "stopping");
|
|
4731
4906
|
this.logger.entity(name).info("Graceful shutdown started");
|
|
4732
4907
|
this.lifecycleEvents.componentStopping(name);
|
|
4908
|
+
const stopAttemptToken = this.issueStopAttemptToken(name);
|
|
4733
4909
|
const timeoutMS = options?.timeout ?? component.shutdownGracefulTimeoutMS;
|
|
4734
4910
|
let timeoutHandle;
|
|
4735
4911
|
try {
|
|
@@ -4750,7 +4926,16 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4750
4926
|
);
|
|
4751
4927
|
}
|
|
4752
4928
|
}
|
|
4753
|
-
Promise.resolve(stopPromise).
|
|
4929
|
+
Promise.resolve(stopPromise).then(
|
|
4930
|
+
() => this.handleLateStopResolution(
|
|
4931
|
+
name,
|
|
4932
|
+
stopAttemptToken,
|
|
4933
|
+
"graceful"
|
|
4934
|
+
),
|
|
4935
|
+
() => {
|
|
4936
|
+
}
|
|
4937
|
+
// Intentionally ignore errors after timeout
|
|
4938
|
+
).catch(() => {
|
|
4754
4939
|
});
|
|
4755
4940
|
reject(
|
|
4756
4941
|
new ComponentStopTimeoutError({
|
|
@@ -4769,9 +4954,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4769
4954
|
this.stalledComponents.delete(name);
|
|
4770
4955
|
this.updateStartedFlag();
|
|
4771
4956
|
if (this.detachSignalsOnStop && this.runningComponents.size === 0 && this.processSignalManager) {
|
|
4772
|
-
this.logger.info(
|
|
4773
|
-
"Auto-detaching process signals on last component stop"
|
|
4774
|
-
);
|
|
4957
|
+
this.logger.info(LIFECYCLE_MANAGER_LOG_AUTO_DETACH_LAST_COMPONENT_STOP);
|
|
4775
4958
|
this.detachSignals();
|
|
4776
4959
|
}
|
|
4777
4960
|
const timestamps = this.componentTimestamps.get(name) ?? {
|
|
@@ -4794,15 +4977,15 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4794
4977
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
4795
4978
|
this.componentErrors.set(name, err);
|
|
4796
4979
|
if (err instanceof ComponentStopTimeoutError && err.additionalInfo.componentName === name) {
|
|
4797
|
-
this.logger.entity(name).warn(
|
|
4980
|
+
this.logger.entity(name).warn(LIFECYCLE_MANAGER_MESSAGE_GRACEFUL_SHUTDOWN_TIMED_OUT);
|
|
4798
4981
|
this.lifecycleEvents.componentStopTimeout(name, err, {
|
|
4799
4982
|
timeoutMS,
|
|
4800
|
-
reason:
|
|
4983
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_GRACEFUL_SHUTDOWN_TIMED_OUT
|
|
4801
4984
|
});
|
|
4802
4985
|
return {
|
|
4803
4986
|
success: false,
|
|
4804
4987
|
componentName: name,
|
|
4805
|
-
reason:
|
|
4988
|
+
reason: LIFECYCLE_MANAGER_MESSAGE_GRACEFUL_SHUTDOWN_TIMED_OUT,
|
|
4806
4989
|
code: "component_shutdown_timeout",
|
|
4807
4990
|
error: err,
|
|
4808
4991
|
status: this.getComponentStatus(name)
|
|
@@ -4845,8 +5028,6 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4845
5028
|
gracefulTimedOut: context.gracefulTimedOut
|
|
4846
5029
|
}
|
|
4847
5030
|
});
|
|
4848
|
-
const timeoutMS = component.shutdownForceTimeoutMS;
|
|
4849
|
-
let timeoutHandle;
|
|
4850
5031
|
if (!component.onShutdownForce) {
|
|
4851
5032
|
const stallInfo = {
|
|
4852
5033
|
name,
|
|
@@ -4880,6 +5061,9 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4880
5061
|
status: this.getComponentStatus(name)
|
|
4881
5062
|
};
|
|
4882
5063
|
}
|
|
5064
|
+
const timeoutMS = component.shutdownForceTimeoutMS;
|
|
5065
|
+
const { promise: stoppedDuringForcePromise, cleanup: cleanupForceWaiter } = this.createPendingForceStopWaiter(name);
|
|
5066
|
+
let timeoutHandle;
|
|
4883
5067
|
try {
|
|
4884
5068
|
const forcePromise = component.onShutdownForce();
|
|
4885
5069
|
if (timeoutMS > 0) {
|
|
@@ -4898,23 +5082,44 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4898
5082
|
);
|
|
4899
5083
|
}
|
|
4900
5084
|
}
|
|
4901
|
-
|
|
5085
|
+
const forceAttemptToken = this.componentStopAttemptTokens.get(name) ?? ulid2();
|
|
5086
|
+
Promise.resolve(forcePromise).then(
|
|
5087
|
+
() => this.handleLateStopResolution(
|
|
5088
|
+
name,
|
|
5089
|
+
forceAttemptToken,
|
|
5090
|
+
"force"
|
|
5091
|
+
),
|
|
5092
|
+
() => {
|
|
5093
|
+
}
|
|
5094
|
+
// Intentionally ignore errors after timeout
|
|
5095
|
+
).catch(() => {
|
|
4902
5096
|
});
|
|
4903
|
-
reject(
|
|
5097
|
+
reject(
|
|
5098
|
+
new Error(LIFECYCLE_MANAGER_MESSAGE_FORCE_SHUTDOWN_TIMED_OUT)
|
|
5099
|
+
);
|
|
4904
5100
|
}, timeoutMS);
|
|
4905
5101
|
});
|
|
4906
|
-
await Promise.race([
|
|
5102
|
+
await Promise.race([
|
|
5103
|
+
forcePromise,
|
|
5104
|
+
timeoutPromise,
|
|
5105
|
+
stoppedDuringForcePromise
|
|
5106
|
+
]);
|
|
4907
5107
|
} else {
|
|
4908
|
-
await forcePromise;
|
|
5108
|
+
await Promise.race([forcePromise, stoppedDuringForcePromise]);
|
|
5109
|
+
}
|
|
5110
|
+
if (this.componentStates.get(name) === "stopped" && !this.runningComponents.has(name)) {
|
|
5111
|
+
return {
|
|
5112
|
+
success: true,
|
|
5113
|
+
componentName: name,
|
|
5114
|
+
status: this.getComponentStatus(name)
|
|
5115
|
+
};
|
|
4909
5116
|
}
|
|
4910
5117
|
this.componentStates.set(name, "stopped");
|
|
4911
5118
|
this.runningComponents.delete(name);
|
|
4912
5119
|
this.stalledComponents.delete(name);
|
|
4913
5120
|
this.updateStartedFlag();
|
|
4914
5121
|
if (this.detachSignalsOnStop && this.runningComponents.size === 0 && this.processSignalManager) {
|
|
4915
|
-
this.logger.info(
|
|
4916
|
-
"Auto-detaching process signals on last component stop"
|
|
4917
|
-
);
|
|
5122
|
+
this.logger.info(LIFECYCLE_MANAGER_LOG_AUTO_DETACH_LAST_COMPONENT_STOP);
|
|
4918
5123
|
this.detachSignals();
|
|
4919
5124
|
}
|
|
4920
5125
|
const timestamps = this.componentTimestamps.get(name) ?? {
|
|
@@ -4935,8 +5140,15 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4935
5140
|
status: this.getComponentStatus(name)
|
|
4936
5141
|
};
|
|
4937
5142
|
} catch (error) {
|
|
5143
|
+
if (this.componentStates.get(name) === "stopped" && !this.runningComponents.has(name)) {
|
|
5144
|
+
return {
|
|
5145
|
+
success: true,
|
|
5146
|
+
componentName: name,
|
|
5147
|
+
status: this.getComponentStatus(name)
|
|
5148
|
+
};
|
|
5149
|
+
}
|
|
4938
5150
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
4939
|
-
const isTimeout = err.message ===
|
|
5151
|
+
const isTimeout = err.message === LIFECYCLE_MANAGER_MESSAGE_FORCE_SHUTDOWN_TIMED_OUT;
|
|
4940
5152
|
const stallInfo = {
|
|
4941
5153
|
name,
|
|
4942
5154
|
phase: "force",
|
|
@@ -4967,12 +5179,13 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4967
5179
|
return {
|
|
4968
5180
|
success: false,
|
|
4969
5181
|
componentName: name,
|
|
4970
|
-
reason: isTimeout ?
|
|
5182
|
+
reason: isTimeout ? LIFECYCLE_MANAGER_MESSAGE_FORCE_SHUTDOWN_TIMED_OUT : err.message,
|
|
4971
5183
|
code: isTimeout ? "component_shutdown_timeout" : "unknown_error",
|
|
4972
5184
|
error: err,
|
|
4973
5185
|
status: this.getComponentStatus(name)
|
|
4974
5186
|
};
|
|
4975
5187
|
} finally {
|
|
5188
|
+
cleanupForceWaiter();
|
|
4976
5189
|
if (timeoutHandle) {
|
|
4977
5190
|
clearTimeout(timeoutHandle);
|
|
4978
5191
|
}
|
|
@@ -5049,7 +5262,9 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
5049
5262
|
"Failed to stop component during rollback, continuing: {{error.message}}",
|
|
5050
5263
|
{
|
|
5051
5264
|
params: {
|
|
5052
|
-
error: result.error || new Error(
|
|
5265
|
+
error: result.error || new Error(
|
|
5266
|
+
result.reason || LIFECYCLE_MANAGER_MESSAGE_UNKNOWN_ERROR
|
|
5267
|
+
)
|
|
5053
5268
|
}
|
|
5054
5269
|
}
|
|
5055
5270
|
);
|
|
@@ -5063,10 +5278,13 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
5063
5278
|
}
|
|
5064
5279
|
this.logger.info(`Auto-attaching process signals on ${trigger}`);
|
|
5065
5280
|
this.attachSignals();
|
|
5281
|
+
if (this.isStarting) {
|
|
5282
|
+
this.autoAttachedSignalsDuringStartup = true;
|
|
5283
|
+
}
|
|
5066
5284
|
return true;
|
|
5067
5285
|
}
|
|
5068
5286
|
autoDetachSignalsIfIdle(trigger) {
|
|
5069
|
-
if (!this.detachSignalsOnStop || this.runningComponents.size > 0 || !this.processSignalManager?.getStatus().isAttached) {
|
|
5287
|
+
if (!this.detachSignalsOnStop || this.isStarting || this.runningComponents.size > 0 || !this.processSignalManager?.getStatus().isAttached) {
|
|
5070
5288
|
return;
|
|
5071
5289
|
}
|
|
5072
5290
|
this.logger.info(`Auto-detaching process signals after ${trigger}`);
|
|
@@ -5108,6 +5326,210 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
5108
5326
|
}).catch(() => {
|
|
5109
5327
|
});
|
|
5110
5328
|
}
|
|
5329
|
+
consumeUnexpectedStopsDuringStartup(startedComponents, failedOptionalComponents) {
|
|
5330
|
+
if (this.unexpectedStopsDuringStartup.size === 0) {
|
|
5331
|
+
return { startedComponents: [...startedComponents] };
|
|
5332
|
+
}
|
|
5333
|
+
const remainingStartedComponents = [];
|
|
5334
|
+
let requiredFailure;
|
|
5335
|
+
for (const name of startedComponents) {
|
|
5336
|
+
const startupStopError = this.unexpectedStopsDuringStartup.get(name);
|
|
5337
|
+
if (startupStopError === void 0) {
|
|
5338
|
+
remainingStartedComponents.push(name);
|
|
5339
|
+
continue;
|
|
5340
|
+
}
|
|
5341
|
+
this.unexpectedStopsDuringStartup.delete(name);
|
|
5342
|
+
const error = startupStopError ?? new Error(`Component "${name}" stopped unexpectedly during startup`);
|
|
5343
|
+
const component = this.getComponent(name);
|
|
5344
|
+
if (component?.isOptional()) {
|
|
5345
|
+
if (!failedOptionalComponents.some((entry) => entry.name === name)) {
|
|
5346
|
+
failedOptionalComponents.push({ name, error });
|
|
5347
|
+
}
|
|
5348
|
+
this.logger.entity(name).warn(
|
|
5349
|
+
LIFECYCLE_MANAGER_LOG_OPTIONAL_COMPONENT_UNEXPECTED_STOP_DURING_STARTUP,
|
|
5350
|
+
{
|
|
5351
|
+
params: { error }
|
|
5352
|
+
}
|
|
5353
|
+
);
|
|
5354
|
+
continue;
|
|
5355
|
+
}
|
|
5356
|
+
this.logger.entity(name).error(
|
|
5357
|
+
LIFECYCLE_MANAGER_LOG_REQUIRED_COMPONENT_UNEXPECTED_STOP_DURING_STARTUP,
|
|
5358
|
+
{
|
|
5359
|
+
params: { error }
|
|
5360
|
+
}
|
|
5361
|
+
);
|
|
5362
|
+
requiredFailure ??= { name, error };
|
|
5363
|
+
}
|
|
5364
|
+
return {
|
|
5365
|
+
startedComponents: remainingStartedComponents,
|
|
5366
|
+
requiredFailure
|
|
5367
|
+
};
|
|
5368
|
+
}
|
|
5369
|
+
/**
|
|
5370
|
+
* Issues and returns a unique stop attempt token for a component.
|
|
5371
|
+
*
|
|
5372
|
+
* Each stop attempt (graceful or force-retry) gets a unique token.
|
|
5373
|
+
* The late-resolution handler captures this token in its closure so it can
|
|
5374
|
+
* skip any stall entries that were created by a *later* stop attempt — e.g. a
|
|
5375
|
+
* force-retry that also timed out after the original graceful promise floated
|
|
5376
|
+
* in the background.
|
|
5377
|
+
*/
|
|
5378
|
+
issueStopAttemptToken(name) {
|
|
5379
|
+
const next = ulid2();
|
|
5380
|
+
this.componentStopAttemptTokens.set(name, next);
|
|
5381
|
+
return next;
|
|
5382
|
+
}
|
|
5383
|
+
createPendingForceStopWaiter(name) {
|
|
5384
|
+
let isResolved = false;
|
|
5385
|
+
let waiters = this.pendingForceStopWaiters.get(name);
|
|
5386
|
+
if (!waiters) {
|
|
5387
|
+
waiters = /* @__PURE__ */ new Set();
|
|
5388
|
+
this.pendingForceStopWaiters.set(name, waiters);
|
|
5389
|
+
}
|
|
5390
|
+
let resolveWaiter;
|
|
5391
|
+
const promise = new Promise((resolve) => {
|
|
5392
|
+
resolveWaiter = () => {
|
|
5393
|
+
if (isResolved) {
|
|
5394
|
+
return;
|
|
5395
|
+
}
|
|
5396
|
+
isResolved = true;
|
|
5397
|
+
resolve();
|
|
5398
|
+
};
|
|
5399
|
+
});
|
|
5400
|
+
waiters.add(resolveWaiter);
|
|
5401
|
+
return {
|
|
5402
|
+
promise,
|
|
5403
|
+
cleanup: () => {
|
|
5404
|
+
const pending = this.pendingForceStopWaiters.get(name);
|
|
5405
|
+
if (!pending) {
|
|
5406
|
+
return;
|
|
5407
|
+
}
|
|
5408
|
+
pending.delete(resolveWaiter);
|
|
5409
|
+
if (pending.size === 0) {
|
|
5410
|
+
this.pendingForceStopWaiters.delete(name);
|
|
5411
|
+
}
|
|
5412
|
+
}
|
|
5413
|
+
};
|
|
5414
|
+
}
|
|
5415
|
+
resolvePendingForceStopWaiters(name) {
|
|
5416
|
+
const waiters = this.pendingForceStopWaiters.get(name);
|
|
5417
|
+
if (!waiters || waiters.size === 0) {
|
|
5418
|
+
return;
|
|
5419
|
+
}
|
|
5420
|
+
this.pendingForceStopWaiters.delete(name);
|
|
5421
|
+
for (const resolve of waiters) {
|
|
5422
|
+
resolve();
|
|
5423
|
+
}
|
|
5424
|
+
}
|
|
5425
|
+
/**
|
|
5426
|
+
* Called when a stop promise eventually resolves after its timeout path already fired.
|
|
5427
|
+
*
|
|
5428
|
+
* Usually this means a previously stalled component's original stop() or
|
|
5429
|
+
* onShutdownForce() promise finally resolved, so the manager can clear the
|
|
5430
|
+
* stall and transition the component to stopped without a manual retry.
|
|
5431
|
+
*
|
|
5432
|
+
* There is one extra overlap case for graceful stop(): stop() can resolve
|
|
5433
|
+
* after the graceful timeout but before onShutdownForce() itself times out.
|
|
5434
|
+
* In that window no stall entry exists yet, but the component still finished
|
|
5435
|
+
* stopping cleanly, so we finalize it here and let the later force-timeout
|
|
5436
|
+
* path observe the already-stopped state and no-op. This overlap fix is
|
|
5437
|
+
* scoped to the same stop token and will not cross a later retry attempt.
|
|
5438
|
+
*
|
|
5439
|
+
* Two guards prevent stale floating promises from incorrectly clearing state:
|
|
5440
|
+
*
|
|
5441
|
+
* 1. token guard — if a newer stop attempt (e.g. a retryStalled
|
|
5442
|
+
* force-retry) has started since this promise was launched, its token
|
|
5443
|
+
* won't match and we bail out immediately.
|
|
5444
|
+
*
|
|
5445
|
+
* 2. state/stall guard — if the component was unregistered, restarted, or
|
|
5446
|
+
* already cleared by another path, there will be neither a matching stall
|
|
5447
|
+
* entry nor the force-phase overlap state, so we bail out.
|
|
5448
|
+
*/
|
|
5449
|
+
handleLateStopResolution(name, token, source) {
|
|
5450
|
+
if (this.componentStopAttemptTokens.get(name) !== token) {
|
|
5451
|
+
return;
|
|
5452
|
+
}
|
|
5453
|
+
const currentState = this.componentStates.get(name);
|
|
5454
|
+
const stallInfo = this.stalledComponents.get(name);
|
|
5455
|
+
const isCompletedDuringForcePhase = source === "graceful" && !stallInfo && currentState === "force-stopping";
|
|
5456
|
+
if (stallInfo && currentState !== "stalled") {
|
|
5457
|
+
this.stalledComponents.delete(name);
|
|
5458
|
+
return;
|
|
5459
|
+
}
|
|
5460
|
+
if (!stallInfo && !isCompletedDuringForcePhase) {
|
|
5461
|
+
return;
|
|
5462
|
+
}
|
|
5463
|
+
const stalledDurationMS = stallInfo ? Date.now() - stallInfo.stalledAt : void 0;
|
|
5464
|
+
if (stallInfo) {
|
|
5465
|
+
this.stalledComponents.delete(name);
|
|
5466
|
+
}
|
|
5467
|
+
this.componentStates.set(name, "stopped");
|
|
5468
|
+
this.runningComponents.delete(name);
|
|
5469
|
+
this.componentErrors.set(name, null);
|
|
5470
|
+
this.updateStartedFlag();
|
|
5471
|
+
this.resolvePendingForceStopWaiters(name);
|
|
5472
|
+
if (this.detachSignalsOnStop && this.runningComponents.size === 0 && this.processSignalManager) {
|
|
5473
|
+
this.logger.info(LIFECYCLE_MANAGER_LOG_AUTO_DETACH_LAST_COMPONENT_STOP);
|
|
5474
|
+
this.detachSignals();
|
|
5475
|
+
}
|
|
5476
|
+
const timestamps = this.componentTimestamps.get(name) ?? {
|
|
5477
|
+
startedAt: null,
|
|
5478
|
+
stoppedAt: null
|
|
5479
|
+
};
|
|
5480
|
+
timestamps.stoppedAt = Date.now();
|
|
5481
|
+
this.componentTimestamps.set(name, timestamps);
|
|
5482
|
+
this.logger.entity(name).info(
|
|
5483
|
+
stallInfo ? "Stalled component completed stop late, stall cleared" : "Graceful stop completed after force phase started",
|
|
5484
|
+
stalledDurationMS ? { params: { stalledDurationMS } } : void 0
|
|
5485
|
+
);
|
|
5486
|
+
if (source === "force") {
|
|
5487
|
+
this.lifecycleEvents.componentShutdownForceCompleted(name);
|
|
5488
|
+
}
|
|
5489
|
+
if (stallInfo && stalledDurationMS !== void 0) {
|
|
5490
|
+
this.lifecycleEvents.componentStalledResolved(
|
|
5491
|
+
name,
|
|
5492
|
+
stallInfo,
|
|
5493
|
+
stalledDurationMS
|
|
5494
|
+
);
|
|
5495
|
+
}
|
|
5496
|
+
this.lifecycleEvents.componentStopped(name, this.getComponentStatus(name));
|
|
5497
|
+
}
|
|
5498
|
+
handleComponentUnexpectedStop(name, startAttemptToken, error) {
|
|
5499
|
+
const currentState = this.componentStates.get(name);
|
|
5500
|
+
if (
|
|
5501
|
+
// Startup-time self-stops are valid too: start() may still be awaiting
|
|
5502
|
+
// some async work while an internal listener has already observed that
|
|
5503
|
+
// the component died and reported it.
|
|
5504
|
+
currentState !== "starting" && currentState !== "running" || this.componentStartAttemptTokens.get(name) !== startAttemptToken
|
|
5505
|
+
) {
|
|
5506
|
+
return false;
|
|
5507
|
+
}
|
|
5508
|
+
this.runningComponents.delete(name);
|
|
5509
|
+
this.componentStates.set(name, "stopped");
|
|
5510
|
+
this.componentErrors.set(name, error ?? null);
|
|
5511
|
+
if (this.isStarting) {
|
|
5512
|
+
this.unexpectedStopsDuringStartup.set(name, error ?? null);
|
|
5513
|
+
}
|
|
5514
|
+
this.updateStartedFlag();
|
|
5515
|
+
if (this.detachSignalsOnStop && !this.isStarting && this.runningComponents.size === 0 && this.processSignalManager) {
|
|
5516
|
+
this.logger.info(LIFECYCLE_MANAGER_LOG_AUTO_DETACH_LAST_COMPONENT_STOP);
|
|
5517
|
+
this.detachSignals();
|
|
5518
|
+
}
|
|
5519
|
+
const timestamps = this.componentTimestamps.get(name) ?? {
|
|
5520
|
+
startedAt: null,
|
|
5521
|
+
stoppedAt: null
|
|
5522
|
+
};
|
|
5523
|
+
timestamps.stoppedAt = Date.now();
|
|
5524
|
+
this.componentTimestamps.set(name, timestamps);
|
|
5525
|
+
this.logger.entity(name).warn(
|
|
5526
|
+
error ? `Component stopped unexpectedly: ${error.message}` : "Component stopped unexpectedly",
|
|
5527
|
+
{ params: { error } }
|
|
5528
|
+
);
|
|
5529
|
+
this.lifecycleEvents.componentUnexpectedStop(name, error);
|
|
5530
|
+
this.lifecycleEvents.componentStopped(name, this.getComponentStatus(name));
|
|
5531
|
+
return true;
|
|
5532
|
+
}
|
|
5111
5533
|
/**
|
|
5112
5534
|
* Safe emit wrapper - prevents event handler errors from breaking lifecycle
|
|
5113
5535
|
*/
|
|
@@ -5679,105 +6101,87 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
5679
6101
|
}
|
|
5680
6102
|
}
|
|
5681
6103
|
/**
|
|
5682
|
-
*
|
|
6104
|
+
* Shared dispatch path for reload/info/debug requests. Logs the dispatch,
|
|
6105
|
+
* emits the signal event, then either invokes the user-supplied callback
|
|
6106
|
+
* (passing the broadcast function so the user controls when/whether to
|
|
6107
|
+
* broadcast) or broadcasts directly when no callback is configured.
|
|
5683
6108
|
*
|
|
5684
6109
|
* When called from signal handlers (source='signal'), the Promise is started
|
|
5685
|
-
* but not awaited
|
|
5686
|
-
* still notified and the work completes
|
|
5687
|
-
*
|
|
6110
|
+
* but not awaited — Node.js signal handlers cannot return values, so results
|
|
6111
|
+
* are not accessible. Components are still notified and the work completes.
|
|
5688
6112
|
* When called from manual triggers (source='trigger'), the Promise is awaited
|
|
5689
6113
|
* and results are returned for programmatic use.
|
|
5690
|
-
*
|
|
5691
|
-
* @param source - Whether triggered from signal manager or manual trigger
|
|
5692
6114
|
*/
|
|
5693
|
-
async
|
|
5694
|
-
this.logger.info(
|
|
5695
|
-
|
|
5696
|
-
if (
|
|
5697
|
-
const
|
|
5698
|
-
const result = this.onReloadRequested(broadcastFn);
|
|
6115
|
+
async handleSignalRequest(descriptor, source) {
|
|
6116
|
+
this.logger.info(descriptor.dispatchedLogLabel, { params: { source } });
|
|
6117
|
+
descriptor.emitSignal();
|
|
6118
|
+
if (descriptor.customCallback) {
|
|
6119
|
+
const result = descriptor.customCallback(descriptor.broadcast);
|
|
5699
6120
|
if (isPromise(result)) {
|
|
5700
6121
|
await result;
|
|
5701
6122
|
}
|
|
5702
6123
|
return {
|
|
5703
|
-
signal:
|
|
6124
|
+
signal: descriptor.signal,
|
|
5704
6125
|
results: [],
|
|
5705
6126
|
timedOut: false,
|
|
5706
6127
|
code: "ok"
|
|
5707
6128
|
};
|
|
5708
6129
|
}
|
|
5709
|
-
return
|
|
6130
|
+
return descriptor.broadcast();
|
|
6131
|
+
}
|
|
6132
|
+
async handleReloadRequest(source = "trigger") {
|
|
6133
|
+
return this.handleSignalRequest(
|
|
6134
|
+
{
|
|
6135
|
+
signal: "reload",
|
|
6136
|
+
dispatchedLogLabel: "Reload dispatched",
|
|
6137
|
+
emitSignal: () => this.lifecycleEvents.signalReload(),
|
|
6138
|
+
customCallback: this.onReloadRequested,
|
|
6139
|
+
broadcast: () => this.broadcastReload()
|
|
6140
|
+
},
|
|
6141
|
+
source
|
|
6142
|
+
);
|
|
5710
6143
|
}
|
|
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
6144
|
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 {
|
|
6145
|
+
return this.handleSignalRequest(
|
|
6146
|
+
{
|
|
5729
6147
|
signal: "info",
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
6148
|
+
dispatchedLogLabel: "Info dispatched",
|
|
6149
|
+
emitSignal: () => this.lifecycleEvents.signalInfo(),
|
|
6150
|
+
customCallback: this.onInfoRequested,
|
|
6151
|
+
broadcast: () => this.broadcastInfo()
|
|
6152
|
+
},
|
|
6153
|
+
source
|
|
6154
|
+
);
|
|
5736
6155
|
}
|
|
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
6156
|
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 {
|
|
6157
|
+
return this.handleSignalRequest(
|
|
6158
|
+
{
|
|
5755
6159
|
signal: "debug",
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
6160
|
+
dispatchedLogLabel: "Debug dispatched",
|
|
6161
|
+
emitSignal: () => this.lifecycleEvents.signalDebug(),
|
|
6162
|
+
customCallback: this.onDebugRequested,
|
|
6163
|
+
broadcast: () => this.broadcastDebug()
|
|
6164
|
+
},
|
|
6165
|
+
source
|
|
6166
|
+
);
|
|
5762
6167
|
}
|
|
5763
6168
|
/**
|
|
5764
|
-
*
|
|
5765
|
-
*
|
|
5766
|
-
*
|
|
6169
|
+
* Shared signal broadcast pipeline used by reload/info/debug.
|
|
6170
|
+
* Iterates running components, runs the picked handler with timeout, and
|
|
6171
|
+
* aggregates per-component results into a SignalBroadcastResult.
|
|
5767
6172
|
*/
|
|
5768
|
-
async
|
|
6173
|
+
async runSignalBroadcast(descriptor) {
|
|
5769
6174
|
const results = [];
|
|
5770
|
-
const
|
|
6175
|
+
const targets = this.components.filter(
|
|
5771
6176
|
(component) => this.runningComponents.has(component.getName())
|
|
5772
6177
|
);
|
|
5773
6178
|
if (this.isStarting) {
|
|
5774
|
-
this.logger.info(
|
|
5775
|
-
"Reload during startup: only reloading already-started components"
|
|
5776
|
-
);
|
|
6179
|
+
this.logger.info(descriptor.startupLog);
|
|
5777
6180
|
}
|
|
5778
|
-
for (const component of
|
|
6181
|
+
for (const component of targets) {
|
|
5779
6182
|
const name = component.getName();
|
|
5780
|
-
|
|
6183
|
+
const handler = descriptor.pickHandler(component);
|
|
6184
|
+
if (!handler) {
|
|
5781
6185
|
results.push({
|
|
5782
6186
|
name,
|
|
5783
6187
|
called: false,
|
|
@@ -5787,13 +6191,13 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
5787
6191
|
});
|
|
5788
6192
|
continue;
|
|
5789
6193
|
}
|
|
5790
|
-
|
|
6194
|
+
descriptor.emitStarted(name);
|
|
5791
6195
|
const timeoutMS = component.signalTimeoutMS;
|
|
5792
6196
|
let timeoutHandle;
|
|
5793
6197
|
const timeoutResult = { timedOut: true };
|
|
5794
6198
|
try {
|
|
5795
|
-
const
|
|
5796
|
-
const handlerPromise = isPromise(
|
|
6199
|
+
const handlerResult = handler();
|
|
6200
|
+
const handlerPromise = isPromise(handlerResult) ? handlerResult : Promise.resolve(handlerResult);
|
|
5797
6201
|
const outcome = timeoutMS > 0 ? await Promise.race([
|
|
5798
6202
|
handlerPromise,
|
|
5799
6203
|
new Promise((resolve) => {
|
|
@@ -5803,7 +6207,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
5803
6207
|
})
|
|
5804
6208
|
]) : await handlerPromise;
|
|
5805
6209
|
if (outcome === timeoutResult) {
|
|
5806
|
-
this.logger.entity(name).warn(
|
|
6210
|
+
this.logger.entity(name).warn(descriptor.timeoutLog, {
|
|
5807
6211
|
params: { timeoutMS }
|
|
5808
6212
|
});
|
|
5809
6213
|
Promise.resolve(handlerPromise).catch(() => {
|
|
@@ -5816,7 +6220,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
5816
6220
|
code: "timeout"
|
|
5817
6221
|
});
|
|
5818
6222
|
} else {
|
|
5819
|
-
|
|
6223
|
+
descriptor.emitCompleted(name);
|
|
5820
6224
|
results.push({
|
|
5821
6225
|
name,
|
|
5822
6226
|
called: true,
|
|
@@ -5827,10 +6231,10 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
5827
6231
|
}
|
|
5828
6232
|
} catch (error) {
|
|
5829
6233
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
5830
|
-
this.logger.entity(name).error(
|
|
6234
|
+
this.logger.entity(name).error(descriptor.errorLog, {
|
|
5831
6235
|
params: { error: err }
|
|
5832
6236
|
});
|
|
5833
|
-
|
|
6237
|
+
descriptor.emitFailed(name, err);
|
|
5834
6238
|
results.push({
|
|
5835
6239
|
name,
|
|
5836
6240
|
called: true,
|
|
@@ -5851,108 +6255,45 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
5851
6255
|
const isAllTimeout = calledResults.length > 0 && calledResults.every((result) => result.timedOut);
|
|
5852
6256
|
const code = hasError ? isAllError ? "error" : "partial_error" : hasTimeout ? isAllTimeout ? "timeout" : "partial_timeout" : "ok";
|
|
5853
6257
|
return {
|
|
5854
|
-
signal:
|
|
6258
|
+
signal: descriptor.signal,
|
|
5855
6259
|
results,
|
|
5856
6260
|
timedOut: hasTimeout,
|
|
5857
6261
|
code
|
|
5858
6262
|
};
|
|
5859
6263
|
}
|
|
6264
|
+
/**
|
|
6265
|
+
* Broadcast reload signal to all running components.
|
|
6266
|
+
* Calls onReload() on components that implement it.
|
|
6267
|
+
* Continues on errors - collects all results.
|
|
6268
|
+
*/
|
|
6269
|
+
async broadcastReload() {
|
|
6270
|
+
return this.runSignalBroadcast({
|
|
6271
|
+
signal: "reload",
|
|
6272
|
+
pickHandler: (component) => component.onReload?.bind(component),
|
|
6273
|
+
startupLog: "Reload during startup: only reloading already-started components",
|
|
6274
|
+
timeoutLog: "Reload handler timed out",
|
|
6275
|
+
errorLog: "Reload failed: {{error.message}}",
|
|
6276
|
+
emitStarted: (name) => this.lifecycleEvents.componentReloadStarted(name),
|
|
6277
|
+
emitCompleted: (name) => this.lifecycleEvents.componentReloadCompleted(name),
|
|
6278
|
+
emitFailed: (name, error) => this.lifecycleEvents.componentReloadFailed(name, error)
|
|
6279
|
+
});
|
|
6280
|
+
}
|
|
5860
6281
|
/**
|
|
5861
6282
|
* Broadcast info signal to all running components.
|
|
5862
6283
|
* Calls onInfo() on components that implement it.
|
|
5863
6284
|
* Continues on errors - collects all results.
|
|
5864
6285
|
*/
|
|
5865
6286
|
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 {
|
|
6287
|
+
return this.runSignalBroadcast({
|
|
5951
6288
|
signal: "info",
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
6289
|
+
pickHandler: (component) => component.onInfo?.bind(component),
|
|
6290
|
+
startupLog: "Info during startup: only notifying already-started components",
|
|
6291
|
+
timeoutLog: "Info handler timed out",
|
|
6292
|
+
errorLog: "Info handler failed: {{error.message}}",
|
|
6293
|
+
emitStarted: (name) => this.lifecycleEvents.componentInfoStarted(name),
|
|
6294
|
+
emitCompleted: (name) => this.lifecycleEvents.componentInfoCompleted(name),
|
|
6295
|
+
emitFailed: (name, error) => this.lifecycleEvents.componentInfoFailed(name, error)
|
|
6296
|
+
});
|
|
5956
6297
|
}
|
|
5957
6298
|
/**
|
|
5958
6299
|
* Broadcast debug signal to all running components.
|
|
@@ -5960,96 +6301,16 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
5960
6301
|
* Continues on errors - collects all results.
|
|
5961
6302
|
*/
|
|
5962
6303
|
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 {
|
|
6304
|
+
return this.runSignalBroadcast({
|
|
6048
6305
|
signal: "debug",
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6306
|
+
pickHandler: (component) => component.onDebug?.bind(component),
|
|
6307
|
+
startupLog: "Debug during startup: only notifying already-started components",
|
|
6308
|
+
timeoutLog: "Debug handler timed out",
|
|
6309
|
+
errorLog: "Debug handler failed: {{error.message}}",
|
|
6310
|
+
emitStarted: (name) => this.lifecycleEvents.componentDebugStarted(name),
|
|
6311
|
+
emitCompleted: (name) => this.lifecycleEvents.componentDebugCompleted(name),
|
|
6312
|
+
emitFailed: (name, error) => this.lifecycleEvents.componentDebugFailed(name, error)
|
|
6313
|
+
});
|
|
6053
6314
|
}
|
|
6054
6315
|
};
|
|
6055
6316
|
|
|
@@ -6075,6 +6336,10 @@ var BaseComponent = class {
|
|
|
6075
6336
|
name;
|
|
6076
6337
|
/** Reference to component-scoped lifecycle (set by manager when registered) */
|
|
6077
6338
|
lifecycle;
|
|
6339
|
+
/** @internal Set by LifecycleManager while the component is running. */
|
|
6340
|
+
_unexpectedStopHandler;
|
|
6341
|
+
/** @internal Incremented whenever the unexpected-stop handler is re-armed or cleared. */
|
|
6342
|
+
_unexpectedStopGeneration = 0;
|
|
6078
6343
|
/**
|
|
6079
6344
|
* Create a new component
|
|
6080
6345
|
*
|
|
@@ -6109,6 +6374,24 @@ var BaseComponent = class {
|
|
|
6109
6374
|
// Default if undefined/null/non-finite
|
|
6110
6375
|
);
|
|
6111
6376
|
}
|
|
6377
|
+
/** @internal Called by LifecycleManager after a successful start. */
|
|
6378
|
+
_setUnexpectedStopHandler(handler) {
|
|
6379
|
+
this._unexpectedStopGeneration += 1;
|
|
6380
|
+
this._unexpectedStopHandler = handler;
|
|
6381
|
+
const generation = this._unexpectedStopGeneration;
|
|
6382
|
+
this.reportUnexpectedStop = (error) => {
|
|
6383
|
+
if (this._unexpectedStopGeneration !== generation) {
|
|
6384
|
+
return false;
|
|
6385
|
+
}
|
|
6386
|
+
return this._unexpectedStopHandler?.(error) ?? false;
|
|
6387
|
+
};
|
|
6388
|
+
}
|
|
6389
|
+
/** @internal Called by LifecycleManager when stop begins or component is unregistered. */
|
|
6390
|
+
_clearUnexpectedStopHandler() {
|
|
6391
|
+
this._unexpectedStopGeneration += 1;
|
|
6392
|
+
this._unexpectedStopHandler = void 0;
|
|
6393
|
+
this.reportUnexpectedStop = () => false;
|
|
6394
|
+
}
|
|
6112
6395
|
/**
|
|
6113
6396
|
* Get component name
|
|
6114
6397
|
*/
|
|
@@ -6127,6 +6410,38 @@ var BaseComponent = class {
|
|
|
6127
6410
|
isOptional() {
|
|
6128
6411
|
return this.optional;
|
|
6129
6412
|
}
|
|
6413
|
+
/**
|
|
6414
|
+
* Run-scoped unexpected-stop callback. Rebound by LifecycleManager on each
|
|
6415
|
+
* successful start so captured references from older runs go stale.
|
|
6416
|
+
*/
|
|
6417
|
+
reportUnexpectedStop = () => false;
|
|
6418
|
+
/**
|
|
6419
|
+
* Get this component's own status from the manager's perspective.
|
|
6420
|
+
*
|
|
6421
|
+
* Equivalent to `this.lifecycle.getComponentStatus(this.getName())` but without
|
|
6422
|
+
* needing to pass the name. Returns `undefined` if the component is not registered.
|
|
6423
|
+
*
|
|
6424
|
+
* Check `status?.state === 'running'` to test whether the component is currently running.
|
|
6425
|
+
*/
|
|
6426
|
+
getSelfStatus() {
|
|
6427
|
+
return this.lifecycle?.getComponentStatus(this.name);
|
|
6428
|
+
}
|
|
6429
|
+
/**
|
|
6430
|
+
* Capture a run-scoped unexpected-stop reporter for async listeners created during start().
|
|
6431
|
+
*
|
|
6432
|
+
* Unlike calling `this.reportUnexpectedStop()` later, the returned callback becomes a no-op
|
|
6433
|
+
* once the component is stopped, unregistered, or restarted. This prevents stale listeners
|
|
6434
|
+
* from a previous run from stopping a newer run of the same component instance.
|
|
6435
|
+
*/
|
|
6436
|
+
getUnexpectedStopReporter() {
|
|
6437
|
+
const generation = this._unexpectedStopGeneration;
|
|
6438
|
+
return (error) => {
|
|
6439
|
+
if (this._unexpectedStopGeneration !== generation) {
|
|
6440
|
+
return false;
|
|
6441
|
+
}
|
|
6442
|
+
return this._unexpectedStopHandler?.(error) ?? false;
|
|
6443
|
+
};
|
|
6444
|
+
}
|
|
6130
6445
|
};
|
|
6131
6446
|
export {
|
|
6132
6447
|
BaseComponent,
|