@znight/core 0.6.3 → 0.6.4
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 -1
- package/dist/index.mjs +36 -4
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/application.ts +54 -5
package/README.md
CHANGED
|
@@ -156,7 +156,8 @@ begins. If a step fails, what was started is stopped, everything is disposed and
|
|
|
156
156
|
**`app.stop()`** is idempotent and waits for an in-progress start, at most `hookTimeoutMs` (a start
|
|
157
157
|
hook that never settles cannot block the shutdown: past that budget, a `Waiting for start() timed out`
|
|
158
158
|
failure is recorded, the remaining start hooks are skipped and what completed its `onStart` is
|
|
159
|
-
stopped
|
|
159
|
+
stopped; a plugin whose `onStart` is still in flight is stopped and disposed when that hook settles,
|
|
160
|
+
not before). It runs three phases:
|
|
160
161
|
|
|
161
162
|
1. plugins' `onStop`, in reverse order (stop ingress, drain in-flight work);
|
|
162
163
|
2. for each provider in reverse resolution order: `onStop` (if it was started), then disposal;
|
package/dist/index.mjs
CHANGED
|
@@ -1956,6 +1956,13 @@ var Application = class Application {
|
|
|
1956
1956
|
#startAborted = false;
|
|
1957
1957
|
/** Releases a `stop()` waiting for the start, when a start hook asks to stop in turn. */
|
|
1958
1958
|
#releaseStopWait;
|
|
1959
|
+
/** Owner of the `onStart` hook in progress. */
|
|
1960
|
+
#starting;
|
|
1961
|
+
/**
|
|
1962
|
+
* Owner of the `onStart` hook still in flight when the wait of `stop()` timed out: the shutdown
|
|
1963
|
+
* skips its `onDispose`, and it is stopped and disposed when that hook settles.
|
|
1964
|
+
*/
|
|
1965
|
+
#lateStart;
|
|
1959
1966
|
constructor(options) {
|
|
1960
1967
|
const plugins = [...options.plugins ?? []];
|
|
1961
1968
|
const byName = /* @__PURE__ */ new Map();
|
|
@@ -2089,11 +2096,11 @@ var Application = class Application {
|
|
|
2089
2096
|
async #runStartHooks() {
|
|
2090
2097
|
const providers = this.container.instances();
|
|
2091
2098
|
for (const provider of providers) {
|
|
2092
|
-
if (!await this.#startHook(() => this.#callProvider(provider, "onStart"))) return false;
|
|
2099
|
+
if (!await this.#startHook(() => this.#callProvider(provider, "onStart"), { provider })) return false;
|
|
2093
2100
|
this.#startedProviders.add(provider.token);
|
|
2094
2101
|
}
|
|
2095
2102
|
for (const plugin of this.#plugins) {
|
|
2096
|
-
if (!await this.#startHook(() => this.#callPlugin(plugin, "onStart"))) return false;
|
|
2103
|
+
if (!await this.#startHook(() => this.#callPlugin(plugin, "onStart"), { plugin })) return false;
|
|
2097
2104
|
this.#startedPlugins.push(plugin);
|
|
2098
2105
|
}
|
|
2099
2106
|
for (const provider of providers) if (!await this.#startHook(() => this.#callProvider(provider, "onReady"))) return false;
|
|
@@ -2103,17 +2110,41 @@ var Application = class Application {
|
|
|
2103
2110
|
/**
|
|
2104
2111
|
* Runs one start hook in an async context carrying a fresh marker, so that a `stop()` called from
|
|
2105
2112
|
* it (or from work it awaits) is recognised. Returns `false` when the hook stopped the application.
|
|
2113
|
+
* `owner` is given for `onStart` hooks: one that settles after `stop()` stopped waiting for it is
|
|
2114
|
+
* stopped and disposed then (see `#stopLateStart`).
|
|
2106
2115
|
*/
|
|
2107
|
-
async #startHook(hook) {
|
|
2116
|
+
async #startHook(hook, owner) {
|
|
2108
2117
|
const marker = {};
|
|
2109
2118
|
this.#startHookMarker = marker;
|
|
2119
|
+
this.#starting = owner;
|
|
2120
|
+
let started = false;
|
|
2110
2121
|
try {
|
|
2111
2122
|
await startHookContext.run(marker, hook);
|
|
2123
|
+
started = true;
|
|
2112
2124
|
} finally {
|
|
2113
2125
|
this.#startHookMarker = void 0;
|
|
2126
|
+
this.#starting = void 0;
|
|
2127
|
+
if (owner !== void 0 && this.#lateStart === owner) await this.#stopLateStart(owner, started);
|
|
2114
2128
|
}
|
|
2115
2129
|
return !this.#startAborted;
|
|
2116
2130
|
}
|
|
2131
|
+
/**
|
|
2132
|
+
* Stops what an `onStart` hook settling after the shutdown started (best effort, failures logged):
|
|
2133
|
+
* a plugin gets `onStop` (when its `onStart` succeeded) then the `onDispose` the shutdown skipped;
|
|
2134
|
+
* a provider, already disposed by the shutdown, gets `onStop` when its `onStart` succeeded.
|
|
2135
|
+
*/
|
|
2136
|
+
async #stopLateStart(owner, started) {
|
|
2137
|
+
const { plugin, provider } = owner;
|
|
2138
|
+
const label = plugin === void 0 ? `${provider?.displayName}.onStart()` : `Plugin "${plugin.name}" onStart()`;
|
|
2139
|
+
this.#logger.warn(`${label} settled after the shutdown stopped waiting for it: stopping it now`);
|
|
2140
|
+
const errors = [];
|
|
2141
|
+
const step = this.#stepRunner(errors);
|
|
2142
|
+
if (plugin !== void 0) {
|
|
2143
|
+
if (started && typeof plugin.onStop === "function") await step(`Plugin "${plugin.name}" onStop()`, () => plugin.onStop?.(this.#context));
|
|
2144
|
+
if (typeof plugin.onDispose === "function") await step(`Plugin "${plugin.name}" onDispose()`, () => plugin.onDispose?.(this.#context));
|
|
2145
|
+
} else if (provider !== void 0 && started && hasLifecycleHook(provider.instance, "onStop")) await step(`${provider.displayName}.onStop()`, () => callLifecycleHook(provider.instance, "onStop"));
|
|
2146
|
+
for (const error of errors) this.#logger.error("Failed to stop a late start", { err: error });
|
|
2147
|
+
}
|
|
2117
2148
|
/** Whether the caller runs in the async context of the start hook in progress. */
|
|
2118
2149
|
#calledFromStartHook() {
|
|
2119
2150
|
const marker = this.#startHookMarker;
|
|
@@ -2153,6 +2184,7 @@ var Application = class Application {
|
|
|
2153
2184
|
if (this.#state === "starting") {
|
|
2154
2185
|
this.#startAborted = true;
|
|
2155
2186
|
this.#releaseStopWait = void 0;
|
|
2187
|
+
this.#lateStart = this.#starting;
|
|
2156
2188
|
}
|
|
2157
2189
|
}
|
|
2158
2190
|
if (this.#state === "failed" || this.#state === "stopped") {
|
|
@@ -2195,7 +2227,7 @@ var Application = class Application {
|
|
|
2195
2227
|
} catch (error) {
|
|
2196
2228
|
errors.push(...flatten(error));
|
|
2197
2229
|
}
|
|
2198
|
-
for (const plugin of [...this.#registered].reverse()) if (typeof plugin.onDispose === "function") await step(`Plugin "${plugin.name}" onDispose()`, () => plugin.onDispose?.(this.#context));
|
|
2230
|
+
for (const plugin of [...this.#registered].reverse()) if (typeof plugin.onDispose === "function" && plugin !== this.#lateStart?.plugin) await step(`Plugin "${plugin.name}" onDispose()`, () => plugin.onDispose?.(this.#context));
|
|
2199
2231
|
this.#startedPlugins.length = 0;
|
|
2200
2232
|
this.#startedProviders.clear();
|
|
2201
2233
|
return errors;
|