lifecycleion 0.0.12 → 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 +1057 -334
- package/dist/lib/lifecycle-manager/index.cjs.map +1 -1
- package/dist/lib/lifecycle-manager/index.d.cts +381 -25
- package/dist/lib/lifecycle-manager/index.d.ts +381 -25
- package/dist/lib/lifecycle-manager/index.js +1057 -334
- package/dist/lib/lifecycle-manager/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -158,7 +158,7 @@ interface RestartComponentOptions {
|
|
|
158
158
|
/**
|
|
159
159
|
* Stable, machine-readable failure codes for individual component operations
|
|
160
160
|
*/
|
|
161
|
-
type ComponentOperationFailureCode = 'component_not_found' | 'component_already_running' | 'component_already_starting' | 'component_already_stopping' | 'component_not_running' | 'component_stalled' | 'missing_dependency' | 'dependency_not_running' | 'has_running_dependents' | 'startup_in_progress' | 'shutdown_in_progress' | 'component_startup_timeout' | 'component_shutdown_timeout' | 'restart_stop_failed' | 'restart_start_failed' | 'unknown_error';
|
|
161
|
+
type ComponentOperationFailureCode = 'component_not_found' | 'component_already_running' | 'component_already_starting' | 'component_already_stopping' | 'component_not_running' | 'component_stalled' | 'missing_dependency' | 'dependency_not_running' | 'has_running_dependents' | 'startup_in_progress' | 'shutdown_in_progress' | 'component_unexpected_stop' | 'component_startup_timeout' | 'component_shutdown_timeout' | 'restart_stop_failed' | 'restart_start_failed' | 'unknown_error';
|
|
162
162
|
/**
|
|
163
163
|
* Failure codes for unregister operations
|
|
164
164
|
*/
|
|
@@ -202,7 +202,7 @@ interface StartupResult {
|
|
|
202
202
|
/** Reason for failure (when success is false) */
|
|
203
203
|
reason?: string;
|
|
204
204
|
/** Error code (when success is false) */
|
|
205
|
-
code?: 'already_in_progress' | 'shutdown_in_progress' | 'dependency_cycle' | 'no_components_registered' | 'stalled_components_exist' | 'partial_state' | 'required_component_failed' | 'startup_timeout' | 'unknown_error';
|
|
205
|
+
code?: 'already_in_progress' | 'component_unexpected_stop' | 'shutdown_in_progress' | 'dependency_cycle' | 'no_components_registered' | 'stalled_components_exist' | 'partial_state' | 'required_component_failed' | 'startup_timeout' | 'unknown_error';
|
|
206
206
|
/** Error object (when success is false due to dependency cycle or unknown error) */
|
|
207
207
|
error?: Error;
|
|
208
208
|
/** Total startup duration in milliseconds */
|
|
@@ -471,6 +471,7 @@ interface LifecycleCommon extends EventEmitterSurface {
|
|
|
471
471
|
attachSignals(): void;
|
|
472
472
|
detachSignals(): void;
|
|
473
473
|
getSignalStatus(): LifecycleSignalStatus;
|
|
474
|
+
getShutdownEscalationStatus(): ShutdownEscalationStatus;
|
|
474
475
|
triggerReload(): Promise<SignalBroadcastResult>;
|
|
475
476
|
triggerInfo(): Promise<SignalBroadcastResult>;
|
|
476
477
|
triggerDebug(): Promise<SignalBroadcastResult>;
|
|
@@ -681,6 +682,163 @@ interface LifecycleSignalStatus extends ProcessSignalManagerStatus {
|
|
|
681
682
|
/** How shutdown was triggered (null if not shut down) */
|
|
682
683
|
shutdownMethod: ShutdownMethod | null;
|
|
683
684
|
}
|
|
685
|
+
interface ShutdownEscalationStatusDisabled {
|
|
686
|
+
/** Whether repeated shutdown escalation is configured */
|
|
687
|
+
configured: false;
|
|
688
|
+
/** Whether a shutdown is currently in progress */
|
|
689
|
+
isShuttingDown: boolean;
|
|
690
|
+
/** Escalation cannot be armed when the policy is disabled */
|
|
691
|
+
isArmed: false;
|
|
692
|
+
/** Config fields are unavailable while the policy is disabled */
|
|
693
|
+
forceAfterCount: null;
|
|
694
|
+
withinMS: null;
|
|
695
|
+
armedAfterFailureMS: null;
|
|
696
|
+
armedAfterFailureMSSource: null;
|
|
697
|
+
/** Runtime counters remain empty while the policy is disabled */
|
|
698
|
+
requestCount: 0;
|
|
699
|
+
firstMethod: null;
|
|
700
|
+
latestMethod: null;
|
|
701
|
+
firstRequestAt: null;
|
|
702
|
+
latestRequestAt: null;
|
|
703
|
+
repeatedWindowStartedAt: null;
|
|
704
|
+
armedUntil: null;
|
|
705
|
+
hasTriggeredForceShutdown: false;
|
|
706
|
+
}
|
|
707
|
+
interface ShutdownEscalationStatusEnabled {
|
|
708
|
+
/** Whether repeated shutdown escalation is configured */
|
|
709
|
+
configured: true;
|
|
710
|
+
/** Whether a shutdown is currently in progress */
|
|
711
|
+
isShuttingDown: boolean;
|
|
712
|
+
/** Whether escalation remains armed after an unsuccessful shutdown */
|
|
713
|
+
isArmed: boolean;
|
|
714
|
+
/** Configured force threshold when policy is enabled */
|
|
715
|
+
forceAfterCount: number;
|
|
716
|
+
/** Configured escalation window when policy is enabled */
|
|
717
|
+
withinMS: number;
|
|
718
|
+
/** Effective post-failure armed duration in ms when policy is enabled */
|
|
719
|
+
armedAfterFailureMS: number;
|
|
720
|
+
/** Whether the effective armed duration was explicitly configured or derived from defaults */
|
|
721
|
+
armedAfterFailureMSSource: 'explicit' | 'derived';
|
|
722
|
+
/** Whether manual stopAllComponents() retries continue the armed escalation state */
|
|
723
|
+
countManualRetriesTowardEscalation: boolean;
|
|
724
|
+
/**
|
|
725
|
+
* Number of follow-up shutdown requests counted in the current escalation
|
|
726
|
+
* window (resets when a new window starts). This is a window-local count,
|
|
727
|
+
* not a cumulative total of all requests since shutdown began.
|
|
728
|
+
*/
|
|
729
|
+
requestCount: number;
|
|
730
|
+
/** Method that started the current/most recent shutdown escalation cycle */
|
|
731
|
+
firstMethod: ShutdownMethod | null;
|
|
732
|
+
/** Most recent shutdown request method seen by escalation tracking */
|
|
733
|
+
latestMethod: ShutdownMethod | null;
|
|
734
|
+
/** Timestamp of the first shutdown request that seeded this escalation state */
|
|
735
|
+
firstRequestAt: number | null;
|
|
736
|
+
/** Timestamp of the most recent shutdown request counted by escalation tracking */
|
|
737
|
+
latestRequestAt: number | null;
|
|
738
|
+
/** Timestamp when the current post-start escalation window began */
|
|
739
|
+
repeatedWindowStartedAt: number | null;
|
|
740
|
+
/** Timestamp when armed-after-failure escalation will expire */
|
|
741
|
+
armedUntil: number | null;
|
|
742
|
+
/** Whether onForceShutdown() has already been dispatched for this shutdown cycle */
|
|
743
|
+
hasTriggeredForceShutdown: boolean;
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* Read-only snapshot of repeated shutdown escalation configuration and runtime state.
|
|
747
|
+
*/
|
|
748
|
+
type ShutdownEscalationStatus = ShutdownEscalationStatusDisabled | ShutdownEscalationStatusEnabled;
|
|
749
|
+
/**
|
|
750
|
+
* Context passed to repeated shutdown force handlers.
|
|
751
|
+
*
|
|
752
|
+
* The first request starts graceful shutdown. If additional shutdown requests
|
|
753
|
+
* arrive within the configured time window and the threshold is reached, the
|
|
754
|
+
* LifecycleManager invokes `onForceShutdown()` with this snapshot.
|
|
755
|
+
*/
|
|
756
|
+
interface ForceShutdownContext {
|
|
757
|
+
/** Number of escalation requests counted after shutdown had already started */
|
|
758
|
+
requestCount: number;
|
|
759
|
+
/** Method that started the current shutdown cycle */
|
|
760
|
+
firstMethod: ShutdownMethod;
|
|
761
|
+
/** Most recent shutdown request method */
|
|
762
|
+
latestMethod: ShutdownMethod;
|
|
763
|
+
/** Unix timestamp in ms for the request that started the current shutdown cycle */
|
|
764
|
+
firstRequestAt: number;
|
|
765
|
+
/** Unix timestamp in ms for the most recent request in the current window */
|
|
766
|
+
latestRequestAt: number;
|
|
767
|
+
/**
|
|
768
|
+
* Whether shutdown is still actively running when the force callback fires.
|
|
769
|
+
*
|
|
770
|
+
* This is `true` when escalation fires during an active shutdown (the normal
|
|
771
|
+
* path: operator pressed the shutdown signal multiple times while
|
|
772
|
+
* `stopAllComponents()` was still running).
|
|
773
|
+
*
|
|
774
|
+
* This is `false` when escalation fires from the post-failure armed window
|
|
775
|
+
* (i.e. a previous shutdown attempt already returned unsuccessfully and the
|
|
776
|
+
* manager kept escalation armed briefly). In that path the force callback
|
|
777
|
+
* fires before the follow-up `stopAllComponents()` call is issued, so no
|
|
778
|
+
* shutdown is actively in progress at the moment the callback runs.
|
|
779
|
+
*
|
|
780
|
+
* Check `wasArmedAfterFailure` to distinguish the two cases.
|
|
781
|
+
*/
|
|
782
|
+
isShuttingDown: boolean;
|
|
783
|
+
/**
|
|
784
|
+
* Whether this force-shutdown was triggered from the post-failure armed
|
|
785
|
+
* state rather than during an active shutdown.
|
|
786
|
+
*
|
|
787
|
+
* `true` — a previous shutdown attempt returned unsuccessfully, the manager
|
|
788
|
+
* kept escalation armed, and the force threshold was crossed while
|
|
789
|
+
* that armed window was still open (no shutdown running yet).
|
|
790
|
+
*
|
|
791
|
+
* `false` — the force threshold was crossed while `stopAllComponents()` was
|
|
792
|
+
* actively in progress (the common path).
|
|
793
|
+
*
|
|
794
|
+
* Use this to decide whether your force handler needs to start or re-trigger
|
|
795
|
+
* shutdown, or whether it can assume a shutdown run is already underway.
|
|
796
|
+
*/
|
|
797
|
+
wasArmedAfterFailure: boolean;
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Optional policy for escalating repeated shutdown requests during an already
|
|
801
|
+
* running shutdown.
|
|
802
|
+
*/
|
|
803
|
+
interface RepeatedShutdownRequestPolicy {
|
|
804
|
+
/**
|
|
805
|
+
* Number of shutdown requests required before force escalation runs.
|
|
806
|
+
* Only escalation requests received after shutdown has already started count
|
|
807
|
+
* toward this threshold. The initial request that starts graceful shutdown
|
|
808
|
+
* does not count.
|
|
809
|
+
* @default 3
|
|
810
|
+
*/
|
|
811
|
+
forceAfterCount?: number;
|
|
812
|
+
/**
|
|
813
|
+
* Time window in ms for counting follow-up escalation requests received
|
|
814
|
+
* after shutdown has already started.
|
|
815
|
+
* Requests outside the window start a new escalation window.
|
|
816
|
+
* @default 2000
|
|
817
|
+
*/
|
|
818
|
+
withinMS?: number;
|
|
819
|
+
/**
|
|
820
|
+
* How long escalation should remain armed after an unsuccessful shutdown
|
|
821
|
+
* returns. When omitted, the manager derives it as `withinMS * forceAfterCount`.
|
|
822
|
+
* Set to `0` to disable post-failure arming entirely — the escalation window
|
|
823
|
+
* will not persist once a shutdown attempt returns, and each new request will
|
|
824
|
+
* start a fresh escalation cycle. (Note: `withinMS = 0` is a separate option
|
|
825
|
+
* that controls the width of the active-shutdown escalation window, not this.)
|
|
826
|
+
*/
|
|
827
|
+
armedAfterFailureMS?: number;
|
|
828
|
+
/**
|
|
829
|
+
* If true, manual `stopAllComponents()` retries that happen while the
|
|
830
|
+
* post-failure armed window is still open continue the same escalation state.
|
|
831
|
+
* When false, manual retries always start a fresh escalation cycle.
|
|
832
|
+
* @default false
|
|
833
|
+
*/
|
|
834
|
+
countManualRetriesTowardEscalation?: boolean;
|
|
835
|
+
/**
|
|
836
|
+
* Invoked once per shutdown cycle when repeated requests reach the threshold.
|
|
837
|
+
* Typical uses: final logging, telemetry flush, `logger.exit()`, or
|
|
838
|
+
* `process.exit()` by the application.
|
|
839
|
+
*/
|
|
840
|
+
onForceShutdown: (context: ForceShutdownContext) => void | Promise<void>;
|
|
841
|
+
}
|
|
684
842
|
/**
|
|
685
843
|
* Configuration options for LifecycleManager
|
|
686
844
|
*/
|
|
@@ -711,6 +869,11 @@ interface LifecycleManagerOptions {
|
|
|
711
869
|
onInfoRequested?: (broadcastInfo: () => Promise<SignalBroadcastResult>) => void | Promise<void>;
|
|
712
870
|
/** Custom debug signal handler (called instead of default broadcast, receives broadcast function you can optionally call) */
|
|
713
871
|
onDebugRequested?: (broadcastDebug: () => Promise<SignalBroadcastResult>) => void | Promise<void>;
|
|
872
|
+
/**
|
|
873
|
+
* Optional policy for escalating repeated shutdown requests received while a
|
|
874
|
+
* graceful shutdown is already in progress.
|
|
875
|
+
*/
|
|
876
|
+
repeatedShutdownRequestPolicy?: RepeatedShutdownRequestPolicy;
|
|
714
877
|
}
|
|
715
878
|
|
|
716
879
|
/**
|
|
@@ -793,6 +956,10 @@ declare abstract class BaseComponent {
|
|
|
793
956
|
protected name: string;
|
|
794
957
|
/** Reference to component-scoped lifecycle (set by manager when registered) */
|
|
795
958
|
protected lifecycle: ComponentLifecycleRef;
|
|
959
|
+
/** @internal Set by LifecycleManager while the component is running. */
|
|
960
|
+
private _unexpectedStopHandler?;
|
|
961
|
+
/** @internal Incremented whenever the unexpected-stop handler is re-armed or cleared. */
|
|
962
|
+
private _unexpectedStopGeneration;
|
|
796
963
|
/**
|
|
797
964
|
* Create a new component
|
|
798
965
|
*
|
|
@@ -801,6 +968,10 @@ declare abstract class BaseComponent {
|
|
|
801
968
|
* @throws {InvalidComponentNameError} If name doesn't match kebab-case pattern
|
|
802
969
|
*/
|
|
803
970
|
constructor(rootLogger: Logger, options: ComponentOptions);
|
|
971
|
+
/** @internal Called by LifecycleManager after a successful start. */
|
|
972
|
+
_setUnexpectedStopHandler(handler: (error?: Error) => boolean): void;
|
|
973
|
+
/** @internal Called by LifecycleManager when stop begins or component is unregistered. */
|
|
974
|
+
_clearUnexpectedStopHandler(): void;
|
|
804
975
|
/**
|
|
805
976
|
* Start the component
|
|
806
977
|
*
|
|
@@ -983,6 +1154,28 @@ declare abstract class BaseComponent {
|
|
|
983
1154
|
* Check if component is optional
|
|
984
1155
|
*/
|
|
985
1156
|
isOptional(): boolean;
|
|
1157
|
+
/**
|
|
1158
|
+
* Run-scoped unexpected-stop callback. Rebound by LifecycleManager on each
|
|
1159
|
+
* successful start so captured references from older runs go stale.
|
|
1160
|
+
*/
|
|
1161
|
+
protected reportUnexpectedStop: (error?: Error) => boolean;
|
|
1162
|
+
/**
|
|
1163
|
+
* Get this component's own status from the manager's perspective.
|
|
1164
|
+
*
|
|
1165
|
+
* Equivalent to `this.lifecycle.getComponentStatus(this.getName())` but without
|
|
1166
|
+
* needing to pass the name. Returns `undefined` if the component is not registered.
|
|
1167
|
+
*
|
|
1168
|
+
* Check `status?.state === 'running'` to test whether the component is currently running.
|
|
1169
|
+
*/
|
|
1170
|
+
protected getSelfStatus(): ComponentStatus | undefined;
|
|
1171
|
+
/**
|
|
1172
|
+
* Capture a run-scoped unexpected-stop reporter for async listeners created during start().
|
|
1173
|
+
*
|
|
1174
|
+
* Unlike calling `this.reportUnexpectedStop()` later, the returned callback becomes a no-op
|
|
1175
|
+
* once the component is stopped, unregistered, or restarted. This prevents stale listeners
|
|
1176
|
+
* from a previous run from stopping a newer run of the same component instance.
|
|
1177
|
+
*/
|
|
1178
|
+
protected getUnexpectedStopReporter(): (error?: Error) => boolean;
|
|
986
1179
|
}
|
|
987
1180
|
|
|
988
1181
|
/**
|
|
@@ -1008,6 +1201,7 @@ declare class LifecycleManager extends EventEmitterProtected implements Lifecycl
|
|
|
1008
1201
|
private readonly attachSignalsBeforeStartup;
|
|
1009
1202
|
private readonly attachSignalsOnStart;
|
|
1010
1203
|
private readonly detachSignalsOnStop;
|
|
1204
|
+
private readonly repeatedShutdownRequestPolicy?;
|
|
1011
1205
|
private components;
|
|
1012
1206
|
private runningComponents;
|
|
1013
1207
|
private componentStates;
|
|
@@ -1015,13 +1209,19 @@ declare class LifecycleManager extends EventEmitterProtected implements Lifecycl
|
|
|
1015
1209
|
private componentTimestamps;
|
|
1016
1210
|
private componentErrors;
|
|
1017
1211
|
private componentStartAttemptTokens;
|
|
1212
|
+
private componentStopAttemptTokens;
|
|
1213
|
+
private pendingForceStopWaiters;
|
|
1214
|
+
private unexpectedStopsDuringStartup;
|
|
1018
1215
|
private isStarting;
|
|
1216
|
+
private autoAttachedSignalsDuringStartup;
|
|
1019
1217
|
private isStarted;
|
|
1020
1218
|
private isShuttingDown;
|
|
1021
1219
|
private shutdownToken;
|
|
1022
1220
|
private pendingLoggerExitResolve;
|
|
1023
1221
|
private shutdownMethod;
|
|
1024
1222
|
private lastShutdownResult;
|
|
1223
|
+
private repeatedShutdownExpiryTimer;
|
|
1224
|
+
private repeatedShutdownRequestState;
|
|
1025
1225
|
private processSignalManager;
|
|
1026
1226
|
private readonly onReloadRequested?;
|
|
1027
1227
|
private readonly onInfoRequested?;
|
|
@@ -1214,6 +1414,10 @@ declare class LifecycleManager extends EventEmitterProtected implements Lifecycl
|
|
|
1214
1414
|
* Get status information about signal handling.
|
|
1215
1415
|
*/
|
|
1216
1416
|
getSignalStatus(): LifecycleSignalStatus;
|
|
1417
|
+
/**
|
|
1418
|
+
* Get status information about repeated shutdown escalation configuration and runtime state.
|
|
1419
|
+
*/
|
|
1420
|
+
getShutdownEscalationStatus(): ShutdownEscalationStatus;
|
|
1217
1421
|
/**
|
|
1218
1422
|
* Enable Logger exit hook integration
|
|
1219
1423
|
*
|
|
@@ -1422,6 +1626,45 @@ declare class LifecycleManager extends EventEmitterProtected implements Lifecycl
|
|
|
1422
1626
|
private autoAttachSignals;
|
|
1423
1627
|
private autoDetachSignalsIfIdle;
|
|
1424
1628
|
private monitorLateStartupCompletion;
|
|
1629
|
+
private consumeUnexpectedStopsDuringStartup;
|
|
1630
|
+
/**
|
|
1631
|
+
* Issues and returns a unique stop attempt token for a component.
|
|
1632
|
+
*
|
|
1633
|
+
* Each stop attempt (graceful or force-retry) gets a unique token.
|
|
1634
|
+
* The late-resolution handler captures this token in its closure so it can
|
|
1635
|
+
* skip any stall entries that were created by a *later* stop attempt — e.g. a
|
|
1636
|
+
* force-retry that also timed out after the original graceful promise floated
|
|
1637
|
+
* in the background.
|
|
1638
|
+
*/
|
|
1639
|
+
private issueStopAttemptToken;
|
|
1640
|
+
private createPendingForceStopWaiter;
|
|
1641
|
+
private resolvePendingForceStopWaiters;
|
|
1642
|
+
/**
|
|
1643
|
+
* Called when a stop promise eventually resolves after its timeout path already fired.
|
|
1644
|
+
*
|
|
1645
|
+
* Usually this means a previously stalled component's original stop() or
|
|
1646
|
+
* onShutdownForce() promise finally resolved, so the manager can clear the
|
|
1647
|
+
* stall and transition the component to stopped without a manual retry.
|
|
1648
|
+
*
|
|
1649
|
+
* There is one extra overlap case for graceful stop(): stop() can resolve
|
|
1650
|
+
* after the graceful timeout but before onShutdownForce() itself times out.
|
|
1651
|
+
* In that window no stall entry exists yet, but the component still finished
|
|
1652
|
+
* stopping cleanly, so we finalize it here and let the later force-timeout
|
|
1653
|
+
* path observe the already-stopped state and no-op. This overlap fix is
|
|
1654
|
+
* scoped to the same stop token and will not cross a later retry attempt.
|
|
1655
|
+
*
|
|
1656
|
+
* Two guards prevent stale floating promises from incorrectly clearing state:
|
|
1657
|
+
*
|
|
1658
|
+
* 1. token guard — if a newer stop attempt (e.g. a retryStalled
|
|
1659
|
+
* force-retry) has started since this promise was launched, its token
|
|
1660
|
+
* won't match and we bail out immediately.
|
|
1661
|
+
*
|
|
1662
|
+
* 2. state/stall guard — if the component was unregistered, restarted, or
|
|
1663
|
+
* already cleared by another path, there will be neither a matching stall
|
|
1664
|
+
* entry nor the force-phase overlap state, so we bail out.
|
|
1665
|
+
*/
|
|
1666
|
+
private handleLateStopResolution;
|
|
1667
|
+
private handleComponentUnexpectedStop;
|
|
1425
1668
|
/**
|
|
1426
1669
|
* Safe emit wrapper - prevents event handler errors from breaking lifecycle
|
|
1427
1670
|
*/
|
|
@@ -1470,40 +1713,100 @@ declare class LifecycleManager extends EventEmitterProtected implements Lifecycl
|
|
|
1470
1713
|
private findAllCircularCycles;
|
|
1471
1714
|
/**
|
|
1472
1715
|
* Handle shutdown signal - initiates stopAllComponents().
|
|
1473
|
-
* Double signal protection: if already shutting down, log warning and ignore.
|
|
1474
|
-
*/
|
|
1475
|
-
private handleShutdownRequest;
|
|
1476
|
-
/**
|
|
1477
|
-
* Handle reload request - calls custom callback or broadcasts to components.
|
|
1478
1716
|
*
|
|
1479
|
-
*
|
|
1480
|
-
* but not awaited due to Node.js signal handler constraints. Components are
|
|
1481
|
-
* still notified and the work completes, but return values are not accessible.
|
|
1717
|
+
* Four cases depending on the current shutdown state:
|
|
1482
1718
|
*
|
|
1483
|
-
*
|
|
1484
|
-
*
|
|
1719
|
+
* 1. **Active shutdown** (`isShuttingDown = true`): escalate through the
|
|
1720
|
+
* repeated-shutdown policy if configured, otherwise log and discard.
|
|
1721
|
+
* Emits `signal:shutdown` with `isAlreadyShuttingDown: true` and returns
|
|
1722
|
+
* without starting another shutdown.
|
|
1723
|
+
*
|
|
1724
|
+
* 2. **Armed post-failure** (previous shutdown finished, armed window still
|
|
1725
|
+
* open): count the request toward the escalation window, emit
|
|
1726
|
+
* `signal:shutdown` with `isAlreadyShuttingDown: false`, then start a
|
|
1727
|
+
* new `stopAllComponents()` run to retry.
|
|
1728
|
+
*
|
|
1729
|
+
* 3. **Armed post-failure expired** (armed window opened but has since
|
|
1730
|
+
* elapsed): expire the stale state, treat the request as a fresh
|
|
1731
|
+
* shutdown (falls through to case 4).
|
|
1732
|
+
*
|
|
1733
|
+
* 4. **Fresh shutdown** (no prior shutdown state): seed escalation tracking
|
|
1734
|
+
* if policy is configured, emit `signal:shutdown` with
|
|
1735
|
+
* `isAlreadyShuttingDown: false`, and start `stopAllComponents()`.
|
|
1485
1736
|
*
|
|
1486
|
-
*
|
|
1737
|
+
* In all cases `signal:shutdown` is emitted exactly once.
|
|
1487
1738
|
*/
|
|
1488
|
-
private
|
|
1739
|
+
private handleShutdownRequest;
|
|
1489
1740
|
/**
|
|
1490
|
-
*
|
|
1741
|
+
* Tracks repeated shutdown requests during an active shutdown and optionally
|
|
1742
|
+
* invokes the configured force shutdown callback when the threshold is reached.
|
|
1491
1743
|
*
|
|
1492
|
-
*
|
|
1493
|
-
*
|
|
1744
|
+
* @returns true when the request was consumed as part of the repeated-shutdown
|
|
1745
|
+
* escalation flow, false when the caller should treat it as a fresh shutdown request
|
|
1746
|
+
*/
|
|
1747
|
+
private handleRepeatedShutdownRequest;
|
|
1748
|
+
/**
|
|
1749
|
+
* Clears repeated shutdown request tracking so a new shutdown cycle starts fresh.
|
|
1750
|
+
*/
|
|
1751
|
+
private resetRepeatedShutdownRequestState;
|
|
1752
|
+
/**
|
|
1753
|
+
* Clear any pending expiration timer for the post-failure escalation window.
|
|
1754
|
+
*/
|
|
1755
|
+
private clearRepeatedShutdownExpiryTimer;
|
|
1756
|
+
/**
|
|
1757
|
+
* Returns whether post-failure escalation remains armed after first
|
|
1758
|
+
* normalizing any stale timer-backed state.
|
|
1494
1759
|
*
|
|
1495
|
-
*
|
|
1760
|
+
* The method can expire old armed windows as a side effect because the timer
|
|
1761
|
+
* callback may not have run yet on a delayed event loop. Callers use this
|
|
1762
|
+
* when they need the effective runtime truth, not just the last timer write.
|
|
1496
1763
|
*/
|
|
1497
|
-
private
|
|
1764
|
+
private normalizeRepeatedShutdownRequestStateArmedStatus;
|
|
1498
1765
|
/**
|
|
1499
|
-
*
|
|
1766
|
+
* Transition armed post-failure escalation state into its expired/reset state.
|
|
1767
|
+
*/
|
|
1768
|
+
private expireRepeatedShutdownRequestState;
|
|
1769
|
+
/**
|
|
1770
|
+
* Arms or refreshes the post-failure escalation window and its expiration timer.
|
|
1771
|
+
*/
|
|
1772
|
+
private refreshRepeatedShutdownArmedWindow;
|
|
1773
|
+
/**
|
|
1774
|
+
* Seeds shutdown escalation tracking for a new shutdown cycle.
|
|
1500
1775
|
*
|
|
1501
|
-
*
|
|
1502
|
-
*
|
|
1776
|
+
* The first shutdown trigger starts graceful shutdown and arms escalation with
|
|
1777
|
+
* an effective post-start count of 0. Later shutdown requests can then count
|
|
1778
|
+
* toward the configured force threshold regardless of whether the shutdown
|
|
1779
|
+
* started from a signal, keyboard shortcut, or direct API call.
|
|
1780
|
+
*/
|
|
1781
|
+
private seedRepeatedShutdownRequestState;
|
|
1782
|
+
/**
|
|
1783
|
+
* Preserves a short-lived post-failure escalation window after shutdown
|
|
1784
|
+
* returns unsuccessfully so operators can keep pressing shutdown without
|
|
1785
|
+
* losing the existing force count the moment the graceful attempt finishes.
|
|
1786
|
+
*/
|
|
1787
|
+
private armRepeatedShutdownAfterFailure;
|
|
1788
|
+
/**
|
|
1789
|
+
* Shared dispatch path for reload/info/debug requests. Logs the dispatch,
|
|
1790
|
+
* emits the signal event, then either invokes the user-supplied callback
|
|
1791
|
+
* (passing the broadcast function so the user controls when/whether to
|
|
1792
|
+
* broadcast) or broadcasts directly when no callback is configured.
|
|
1503
1793
|
*
|
|
1504
|
-
*
|
|
1794
|
+
* When called from signal handlers (source='signal'), the Promise is started
|
|
1795
|
+
* but not awaited — Node.js signal handlers cannot return values, so results
|
|
1796
|
+
* are not accessible. Components are still notified and the work completes.
|
|
1797
|
+
* When called from manual triggers (source='trigger'), the Promise is awaited
|
|
1798
|
+
* and results are returned for programmatic use.
|
|
1505
1799
|
*/
|
|
1800
|
+
private handleSignalRequest;
|
|
1801
|
+
private handleReloadRequest;
|
|
1802
|
+
private handleInfoRequest;
|
|
1506
1803
|
private handleDebugRequest;
|
|
1804
|
+
/**
|
|
1805
|
+
* Shared signal broadcast pipeline used by reload/info/debug.
|
|
1806
|
+
* Iterates running components, runs the picked handler with timeout, and
|
|
1807
|
+
* aggregates per-component results into a SignalBroadcastResult.
|
|
1808
|
+
*/
|
|
1809
|
+
private runSignalBroadcast;
|
|
1507
1810
|
/**
|
|
1508
1811
|
* Broadcast reload signal to all running components.
|
|
1509
1812
|
* Calls onReload() on components that implement it.
|
|
@@ -1637,6 +1940,28 @@ interface LifecycleManagerEventMap {
|
|
|
1637
1940
|
method: ShutdownMethod;
|
|
1638
1941
|
duringStartup: boolean;
|
|
1639
1942
|
};
|
|
1943
|
+
/** Failed/timed-out shutdown left escalation armed briefly for follow-up force presses. */
|
|
1944
|
+
'lifecycle-manager:shutdown-escalation-armed': {
|
|
1945
|
+
firstMethod: ShutdownMethod;
|
|
1946
|
+
requestCount: number;
|
|
1947
|
+
armedUntil: number;
|
|
1948
|
+
};
|
|
1949
|
+
/** Armed escalation window expired and the manager cleared that old escalation state. */
|
|
1950
|
+
'lifecycle-manager:shutdown-escalation-expired': {
|
|
1951
|
+
firstMethod: ShutdownMethod;
|
|
1952
|
+
latestMethod: ShutdownMethod | null;
|
|
1953
|
+
requestCount: number;
|
|
1954
|
+
armedUntil: number;
|
|
1955
|
+
};
|
|
1956
|
+
/** Repeated shutdown requests crossed the force threshold and onForceShutdown() was invoked. */
|
|
1957
|
+
'lifecycle-manager:shutdown-escalation-forced': {
|
|
1958
|
+
firstMethod: ShutdownMethod;
|
|
1959
|
+
latestMethod: ShutdownMethod;
|
|
1960
|
+
requestCount: number;
|
|
1961
|
+
firstRequestAt: number;
|
|
1962
|
+
latestRequestAt: number;
|
|
1963
|
+
wasArmedAfterFailure: boolean;
|
|
1964
|
+
};
|
|
1640
1965
|
'component:starting': {
|
|
1641
1966
|
name: string;
|
|
1642
1967
|
};
|
|
@@ -1701,6 +2026,15 @@ interface LifecycleManagerEventMap {
|
|
|
1701
2026
|
reason?: string;
|
|
1702
2027
|
code?: string;
|
|
1703
2028
|
};
|
|
2029
|
+
'component:stalled-resolved': {
|
|
2030
|
+
name: string;
|
|
2031
|
+
stallInfo: ComponentStallInfo;
|
|
2032
|
+
stalledDurationMS: number;
|
|
2033
|
+
};
|
|
2034
|
+
'component:unexpected-stop': {
|
|
2035
|
+
name: string;
|
|
2036
|
+
error?: Error;
|
|
2037
|
+
};
|
|
1704
2038
|
'component:shutdown-force-completed': {
|
|
1705
2039
|
name: string;
|
|
1706
2040
|
};
|
|
@@ -1713,6 +2047,7 @@ interface LifecycleManagerEventMap {
|
|
|
1713
2047
|
};
|
|
1714
2048
|
'signal:shutdown': {
|
|
1715
2049
|
method: ShutdownSignal;
|
|
2050
|
+
isAlreadyShuttingDown: boolean;
|
|
1716
2051
|
};
|
|
1717
2052
|
'signal:reload': undefined;
|
|
1718
2053
|
'signal:info': undefined;
|
|
@@ -1827,6 +2162,25 @@ declare class LifecycleManagerEvents {
|
|
|
1827
2162
|
method: ShutdownMethod;
|
|
1828
2163
|
duringStartup: boolean;
|
|
1829
2164
|
}): void;
|
|
2165
|
+
lifecycleManagerShutdownEscalationArmed(input: {
|
|
2166
|
+
firstMethod: ShutdownMethod;
|
|
2167
|
+
requestCount: number;
|
|
2168
|
+
armedUntil: number;
|
|
2169
|
+
}): void;
|
|
2170
|
+
lifecycleManagerShutdownEscalationExpired(input: {
|
|
2171
|
+
firstMethod: ShutdownMethod;
|
|
2172
|
+
latestMethod: ShutdownMethod | null;
|
|
2173
|
+
requestCount: number;
|
|
2174
|
+
armedUntil: number;
|
|
2175
|
+
}): void;
|
|
2176
|
+
lifecycleManagerShutdownEscalationForced(input: {
|
|
2177
|
+
firstMethod: ShutdownMethod;
|
|
2178
|
+
latestMethod: ShutdownMethod;
|
|
2179
|
+
requestCount: number;
|
|
2180
|
+
firstRequestAt: number;
|
|
2181
|
+
latestRequestAt: number;
|
|
2182
|
+
wasArmedAfterFailure: boolean;
|
|
2183
|
+
}): void;
|
|
1830
2184
|
componentStarting(name: string): void;
|
|
1831
2185
|
componentStarted(name: string, status?: ComponentStatus): void;
|
|
1832
2186
|
componentStartTimeout(name: string, error: Error, info?: {
|
|
@@ -1859,10 +2213,12 @@ declare class LifecycleManagerEvents {
|
|
|
1859
2213
|
reason?: string;
|
|
1860
2214
|
code?: string;
|
|
1861
2215
|
}): void;
|
|
2216
|
+
componentStalledResolved(name: string, stallInfo: ComponentStallInfo, stalledDurationMS: number): void;
|
|
2217
|
+
componentUnexpectedStop(name: string, error?: Error): void;
|
|
1862
2218
|
componentShutdownForceCompleted(name: string): void;
|
|
1863
2219
|
componentShutdownForceTimeout(name: string, timeoutMS: number): void;
|
|
1864
2220
|
componentStartupRollback(name: string): void;
|
|
1865
|
-
signalShutdown(method: ShutdownSignal): void;
|
|
2221
|
+
signalShutdown(method: ShutdownSignal, isAlreadyShuttingDown?: boolean): void;
|
|
1866
2222
|
signalReload(): void;
|
|
1867
2223
|
signalInfo(): void;
|
|
1868
2224
|
signalDebug(): void;
|
|
@@ -2048,4 +2404,4 @@ declare const lifecycleManagerErrCodes: {
|
|
|
2048
2404
|
readonly StopTimeout: "StopTimeout";
|
|
2049
2405
|
};
|
|
2050
2406
|
|
|
2051
|
-
export { BaseComponent, type BaseOperationResult, type BroadcastOptions, type BroadcastResult, type ComponentHealthResult, ComponentNotFoundError, type ComponentOperationFailureCode, type ComponentOperationResult, type ComponentOptions, ComponentRegistrationError, type ComponentSignalResult, type ComponentStallInfo, ComponentStartTimeoutError, ComponentStartupError, type ComponentState, type ComponentStatus, ComponentStopTimeoutError, type ComponentValueResult, DependencyCycleError, type DependencyValidationResult, type GetValueOptions, type HealthCheckResult, type HealthReport, type InsertComponentAtResult, type InsertPosition, InvalidComponentNameError, LifecycleManager, type LifecycleManagerEmit, type LifecycleManagerEventMap, type LifecycleManagerEventName, LifecycleManagerEvents, type LifecycleManagerOptions, type LifecycleManagerStatus, type MessageResult, MissingDependencyError, type RegisterComponentResult, type RegisterOptions, type RegistrationFailureCode, type RestartComponentOptions, type RestartResult, type SendMessageOptions, type ShutdownMethod, type ShutdownResult, type SignalBroadcastResult, type StartComponentOptions, type StartupOptions, type StartupOrderFailureCode, type StartupOrderResult, type StartupResult, StartupTimeoutError, type StopAllOptions, type StopComponentOptions, type SystemState, type UnregisterComponentResult, type UnregisterFailureCode, type UnregisterOptions, type ValueResult, lifecycleManagerErrCodes, lifecycleManagerErrPrefix, lifecycleManagerErrTypes };
|
|
2407
|
+
export { BaseComponent, type BaseOperationResult, type BroadcastOptions, type BroadcastResult, type ComponentHealthResult, ComponentNotFoundError, type ComponentOperationFailureCode, type ComponentOperationResult, type ComponentOptions, ComponentRegistrationError, type ComponentSignalResult, type ComponentStallInfo, ComponentStartTimeoutError, ComponentStartupError, type ComponentState, type ComponentStatus, ComponentStopTimeoutError, type ComponentValueResult, DependencyCycleError, type DependencyValidationResult, type ForceShutdownContext, type GetValueOptions, type HealthCheckResult, type HealthReport, type InsertComponentAtResult, type InsertPosition, InvalidComponentNameError, LifecycleManager, type LifecycleManagerEmit, type LifecycleManagerEventMap, type LifecycleManagerEventName, LifecycleManagerEvents, type LifecycleManagerOptions, type LifecycleManagerStatus, type MessageResult, MissingDependencyError, type RegisterComponentResult, type RegisterOptions, type RegistrationFailureCode, type RepeatedShutdownRequestPolicy, type RestartComponentOptions, type RestartResult, type SendMessageOptions, type ShutdownEscalationStatus, type ShutdownMethod, type ShutdownResult, type SignalBroadcastResult, type StartComponentOptions, type StartupOptions, type StartupOrderFailureCode, type StartupOrderResult, type StartupResult, StartupTimeoutError, type StopAllOptions, type StopComponentOptions, type SystemState, type UnregisterComponentResult, type UnregisterFailureCode, type UnregisterOptions, type ValueResult, lifecycleManagerErrCodes, lifecycleManagerErrPrefix, lifecycleManagerErrTypes };
|