@spine-event-engine/server 2.0.0-snapshot.22 → 2.0.0-snapshot.24
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 +104 -7
- package/REFERENCE.md +272 -31
- package/dist/agent/agent-admission.d.ts +80 -0
- package/dist/agent/agent-admission.d.ts.map +1 -0
- package/dist/agent/agent-admission.js +127 -0
- package/dist/agent/agent-admission.js.map +1 -0
- package/dist/agent/agent-ai-binding.d.ts +24 -0
- package/dist/agent/agent-ai-binding.d.ts.map +1 -0
- package/dist/agent/agent-ai-binding.js +48 -0
- package/dist/agent/agent-ai-binding.js.map +1 -0
- package/dist/agent/agent-ai-runtime.d.ts +76 -0
- package/dist/agent/agent-ai-runtime.d.ts.map +1 -0
- package/dist/agent/agent-ai-runtime.js +1269 -0
- package/dist/agent/agent-ai-runtime.js.map +1 -0
- package/dist/agent/agent-execution-capacity.d.ts +43 -0
- package/dist/agent/agent-execution-capacity.d.ts.map +1 -0
- package/dist/agent/agent-execution-capacity.js +147 -0
- package/dist/agent/agent-execution-capacity.js.map +1 -0
- package/dist/agent/agent-execution-fault.d.ts +18 -0
- package/dist/agent/agent-execution-fault.d.ts.map +1 -0
- package/dist/agent/agent-execution-fault.js +31 -0
- package/dist/agent/agent-execution-fault.js.map +1 -0
- package/dist/agent/agent-execution-session.d.ts +103 -0
- package/dist/agent/agent-execution-session.d.ts.map +1 -0
- package/dist/agent/agent-execution-session.js +238 -0
- package/dist/agent/agent-execution-session.js.map +1 -0
- package/dist/agent/agent-history.d.ts +148 -0
- package/dist/agent/agent-history.d.ts.map +1 -0
- package/dist/agent/agent-history.js +340 -0
- package/dist/agent/agent-history.js.map +1 -0
- package/dist/agent/agent-interaction-audit.d.ts +58 -0
- package/dist/agent/agent-interaction-audit.d.ts.map +1 -0
- package/dist/agent/agent-interaction-audit.js +114 -0
- package/dist/agent/agent-interaction-audit.js.map +1 -0
- package/dist/agent/agent-interaction-events.d.ts +40 -0
- package/dist/agent/agent-interaction-events.d.ts.map +1 -0
- package/dist/agent/agent-interaction-events.js +129 -0
- package/dist/agent/agent-interaction-events.js.map +1 -0
- package/dist/agent/agent-mcp-budget.d.ts +125 -0
- package/dist/agent/agent-mcp-budget.d.ts.map +1 -0
- package/dist/agent/agent-mcp-budget.js +566 -0
- package/dist/agent/agent-mcp-budget.js.map +1 -0
- package/dist/agent/agent-mcp-host.d.ts +125 -0
- package/dist/agent/agent-mcp-host.d.ts.map +1 -0
- package/dist/agent/agent-mcp-host.js +15 -0
- package/dist/agent/agent-mcp-host.js.map +1 -0
- package/dist/agent/agent-mcp-runtime.d.ts +199 -0
- package/dist/agent/agent-mcp-runtime.d.ts.map +1 -0
- package/dist/agent/agent-mcp-runtime.js +621 -0
- package/dist/agent/agent-mcp-runtime.js.map +1 -0
- package/dist/agent/agent-model-selection.d.ts +129 -0
- package/dist/agent/agent-model-selection.d.ts.map +1 -0
- package/dist/agent/agent-model-selection.js +342 -0
- package/dist/agent/agent-model-selection.js.map +1 -0
- package/dist/agent/agent-read-runtime.d.ts +70 -0
- package/dist/agent/agent-read-runtime.d.ts.map +1 -0
- package/dist/agent/agent-read-runtime.js +320 -0
- package/dist/agent/agent-read-runtime.js.map +1 -0
- package/dist/agent/agent-revisions.d.ts +35 -0
- package/dist/agent/agent-revisions.d.ts.map +1 -0
- package/dist/agent/agent-revisions.js +133 -0
- package/dist/agent/agent-revisions.js.map +1 -0
- package/dist/agent/agent-scheduler.d.ts +99 -0
- package/dist/agent/agent-scheduler.d.ts.map +1 -0
- package/dist/agent/agent-scheduler.js +398 -0
- package/dist/agent/agent-scheduler.js.map +1 -0
- package/dist/bus/command-bus.d.ts +62 -0
- package/dist/bus/command-bus.d.ts.map +1 -1
- package/dist/bus/command-bus.js +51 -2
- package/dist/bus/command-bus.js.map +1 -1
- package/dist/bus/event-bus.d.ts +101 -2
- package/dist/bus/event-bus.d.ts.map +1 -1
- package/dist/bus/event-bus.js +74 -13
- package/dist/bus/event-bus.js.map +1 -1
- package/dist/bus/saved-dispatcher-binding.d.ts +53 -0
- package/dist/bus/saved-dispatcher-binding.d.ts.map +1 -0
- package/dist/bus/saved-dispatcher-binding.js +27 -0
- package/dist/bus/saved-dispatcher-binding.js.map +1 -0
- package/dist/context/bounded-context.d.ts +45 -12
- package/dist/context/bounded-context.d.ts.map +1 -1
- package/dist/context/bounded-context.js +306 -86
- package/dist/context/bounded-context.js.map +1 -1
- package/dist/context/entity-inbox.d.ts.map +1 -1
- package/dist/context/entity-inbox.js +21 -7
- package/dist/context/entity-inbox.js.map +1 -1
- package/dist/context/local-inbox-handoff.d.ts +1 -0
- package/dist/context/local-inbox-handoff.d.ts.map +1 -1
- package/dist/context/local-inbox-handoff.js +31 -1
- package/dist/context/local-inbox-handoff.js.map +1 -1
- package/dist/context/projection-handoff.d.ts.map +1 -1
- package/dist/context/projection-handoff.js +2 -0
- package/dist/context/projection-handoff.js.map +1 -1
- package/dist/context/tenant-index.d.ts +25 -0
- package/dist/context/tenant-index.d.ts.map +1 -1
- package/dist/context/tenant-index.js +45 -1
- package/dist/context/tenant-index.js.map +1 -1
- package/dist/delivery/delivery-monitor.d.ts +12 -9
- package/dist/delivery/delivery-monitor.d.ts.map +1 -1
- package/dist/delivery/delivery-monitor.js +15 -9
- package/dist/delivery/delivery-monitor.js.map +1 -1
- package/dist/delivery/delivery-ports.d.ts +6 -13
- package/dist/delivery/delivery-ports.d.ts.map +1 -1
- package/dist/delivery/delivery-run-control.d.ts +15 -0
- package/dist/delivery/delivery-run-control.d.ts.map +1 -1
- package/dist/delivery/delivery-run-control.js.map +1 -1
- package/dist/delivery/delivery-supervisor.d.ts +33 -5
- package/dist/delivery/delivery-supervisor.d.ts.map +1 -1
- package/dist/delivery/delivery-supervisor.js +150 -22
- package/dist/delivery/delivery-supervisor.js.map +1 -1
- package/dist/delivery/delivery.d.ts.map +1 -1
- package/dist/delivery/delivery.js +128 -54
- package/dist/delivery/delivery.js.map +1 -1
- package/dist/delivery/inbox-storage.d.ts +8 -11
- package/dist/delivery/inbox-storage.d.ts.map +1 -1
- package/dist/delivery/inbox-storage.js +33 -76
- package/dist/delivery/inbox-storage.js.map +1 -1
- package/dist/delivery/inbox.d.ts +9 -12
- package/dist/delivery/inbox.d.ts.map +1 -1
- package/dist/delivery/inbox.js +10 -13
- package/dist/delivery/inbox.js.map +1 -1
- package/dist/delivery/reception-observation.d.ts +56 -0
- package/dist/delivery/reception-observation.d.ts.map +1 -0
- package/dist/delivery/reception-observation.js +67 -0
- package/dist/delivery/reception-observation.js.map +1 -0
- package/dist/entity/entity.d.ts +82 -3
- package/dist/entity/entity.d.ts.map +1 -1
- package/dist/entity/entity.js +121 -22
- package/dist/entity/entity.js.map +1 -1
- package/dist/handler/generated-handler-registry.js +8 -3
- package/dist/handler/generated-handler-registry.js.map +1 -1
- package/dist/handler/handler-metadata.d.ts +17 -1
- package/dist/handler/handler-metadata.d.ts.map +1 -1
- package/dist/handler/handler-metadata.js +10 -4
- package/dist/handler/handler-metadata.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/repository/commit-fence.d.ts +11 -0
- package/dist/repository/commit-fence.d.ts.map +1 -1
- package/dist/repository/commit-fence.js +13 -0
- package/dist/repository/commit-fence.js.map +1 -1
- package/dist/repository/repository.d.ts +221 -13
- package/dist/repository/repository.d.ts.map +1 -1
- package/dist/repository/repository.js +2323 -134
- package/dist/repository/repository.js.map +1 -1
- package/dist/runtime/runtime.d.ts +11 -0
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +13 -0
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/signal-metadata.d.ts.map +1 -1
- package/dist/runtime/signal-metadata.js +3 -10
- package/dist/runtime/signal-metadata.js.map +1 -1
- package/dist/runtime/signal-publisher.d.ts +14 -30
- package/dist/runtime/signal-publisher.d.ts.map +1 -1
- package/dist/runtime/signal-publisher.js +75 -47
- package/dist/runtime/signal-publisher.js.map +1 -1
- package/dist/runtime/standalone-handler-runtime.d.ts +7 -0
- package/dist/runtime/standalone-handler-runtime.d.ts.map +1 -1
- package/dist/runtime/standalone-handler-runtime.js +118 -11
- package/dist/runtime/standalone-handler-runtime.js.map +1 -1
- package/dist/server/environment-delivery-worker.d.ts.map +1 -1
- package/dist/server/environment-delivery-worker.js +305 -5
- package/dist/server/environment-delivery-worker.js.map +1 -1
- package/dist/server/managed-server-application.d.ts +2 -2
- package/dist/server/managed-server-application.d.ts.map +1 -1
- package/dist/server/managed-server-application.js +467 -177
- package/dist/server/managed-server-application.js.map +1 -1
- package/dist/server/node-coordinator.d.ts +14 -0
- package/dist/server/node-coordinator.d.ts.map +1 -1
- package/dist/server/node-coordinator.js +266 -45
- package/dist/server/node-coordinator.js.map +1 -1
- package/dist/server/server-log.d.ts.map +1 -1
- package/dist/server/server-log.js +4 -0
- package/dist/server/server-log.js.map +1 -1
- package/dist/server/server.d.ts +9 -0
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +33 -8
- package/dist/server/server.js.map +1 -1
- package/dist/services/spine-services.d.ts.map +1 -1
- package/dist/services/spine-services.js +200 -72
- package/dist/services/spine-services.js.map +1 -1
- package/dist/stand/event-origin-filter.d.ts +16 -0
- package/dist/stand/event-origin-filter.d.ts.map +1 -0
- package/dist/stand/event-origin-filter.js +124 -0
- package/dist/stand/event-origin-filter.js.map +1 -0
- package/dist/stand/subscription-observer.d.ts.map +1 -1
- package/dist/stand/subscription-observer.js +7 -0
- package/dist/stand/subscription-observer.js.map +1 -1
- package/dist/stand/subscription-runtime.d.ts +18 -1
- package/dist/stand/subscription-runtime.d.ts.map +1 -1
- package/dist/stand/subscription-runtime.js +32 -4
- package/dist/stand/subscription-runtime.js.map +1 -1
- package/dist/testing/index.d.ts +57 -1
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +88 -1
- package/dist/testing/index.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +15 -13
|
@@ -106,115 +106,7 @@ const ManagedServerValues = Object.freeze({
|
|
|
106
106
|
}
|
|
107
107
|
throw error;
|
|
108
108
|
}
|
|
109
|
-
|
|
110
|
-
// disconnect handler below handles shutdown; this listener prevents that
|
|
111
|
-
// transport detail from becoming an unhandled child-process exception.
|
|
112
|
-
const containIpcError = () => undefined;
|
|
113
|
-
process.on("error", containIpcError);
|
|
114
|
-
managedChildSubscriptionAccess.install((subscriptionId) => {
|
|
115
|
-
const notification = ManagedServerValues.send({
|
|
116
|
-
type: "subscription-installed",
|
|
117
|
-
slot: process.env[slotMarker],
|
|
118
|
-
incarnation: process.env[incarnationMarker],
|
|
119
|
-
subscriptionId,
|
|
120
|
-
});
|
|
121
|
-
// spine-log-boundary: server.managed_replica_subscription_ipc
|
|
122
|
-
void notification.catch(() => undefined);
|
|
123
|
-
});
|
|
124
|
-
let activated = false;
|
|
125
|
-
let releaseActivation;
|
|
126
|
-
const activation = new Promise((resolve) => {
|
|
127
|
-
releaseActivation = resolve;
|
|
128
|
-
});
|
|
129
|
-
let closing;
|
|
130
|
-
const close = () => {
|
|
131
|
-
if (closing !== undefined)
|
|
132
|
-
return closing;
|
|
133
|
-
const draining = ManagedServerValues.send({
|
|
134
|
-
type: "draining",
|
|
135
|
-
slot: process.env[slotMarker],
|
|
136
|
-
incarnation: process.env[incarnationMarker],
|
|
137
|
-
});
|
|
138
|
-
// spine-log-boundary: server.managed_replica_drain_ipc
|
|
139
|
-
const attempt = draining
|
|
140
|
-
.catch(() => undefined)
|
|
141
|
-
.then(() => {
|
|
142
|
-
environmentDeliveryWorkerAccess.cancelManagedChild();
|
|
143
|
-
managedChildSubscriptionAccess.clear();
|
|
144
|
-
releaseActivation();
|
|
145
|
-
return runningServerAccess.drainDelivery(server);
|
|
146
|
-
})
|
|
147
|
-
.then(() => {
|
|
148
|
-
return server.close();
|
|
149
|
-
})
|
|
150
|
-
.then(() => {
|
|
151
|
-
const closed = ManagedServerValues.send({
|
|
152
|
-
type: "closed",
|
|
153
|
-
slot: process.env[slotMarker],
|
|
154
|
-
incarnation: process.env[incarnationMarker],
|
|
155
|
-
});
|
|
156
|
-
// spine-log-boundary: server.managed_replica_closed_ipc
|
|
157
|
-
return closed.catch(() => undefined);
|
|
158
|
-
}, (error) => {
|
|
159
|
-
const failed = ManagedServerValues.send({
|
|
160
|
-
type: "close-failed",
|
|
161
|
-
slot: process.env[slotMarker],
|
|
162
|
-
incarnation: process.env[incarnationMarker],
|
|
163
|
-
});
|
|
164
|
-
// spine-log-boundary: server.managed_replica_close_failed_ipc
|
|
165
|
-
void failed.catch(() => undefined);
|
|
166
|
-
if (closing === attempt)
|
|
167
|
-
closing = undefined;
|
|
168
|
-
throw error;
|
|
169
|
-
})
|
|
170
|
-
.then(() => {
|
|
171
|
-
process.off("disconnect", onDisconnect);
|
|
172
|
-
if (process.connected && typeof process.disconnect === "function")
|
|
173
|
-
process.disconnect();
|
|
174
|
-
process.off("error", containIpcError);
|
|
175
|
-
process.off("message", onMessage);
|
|
176
|
-
});
|
|
177
|
-
closing = attempt;
|
|
178
|
-
return attempt;
|
|
179
|
-
};
|
|
180
|
-
const onMessage = (message) => {
|
|
181
|
-
if (message.type === "activate" && !activated) {
|
|
182
|
-
activated = true;
|
|
183
|
-
environmentDeliveryWorkerAccess.activateManagedChild();
|
|
184
|
-
releaseActivation();
|
|
185
|
-
}
|
|
186
|
-
else if (message.type === "close") {
|
|
187
|
-
ManagedServerValues.closeFromEvent(close);
|
|
188
|
-
}
|
|
189
|
-
};
|
|
190
|
-
const onDisconnect = () => {
|
|
191
|
-
ManagedServerValues.closeFromEvent(close);
|
|
192
|
-
};
|
|
193
|
-
process.on("message", onMessage);
|
|
194
|
-
process.once("disconnect", onDisconnect);
|
|
195
|
-
try {
|
|
196
|
-
await ManagedServerValues.send({
|
|
197
|
-
type: "synchronizing",
|
|
198
|
-
slot: process.env[slotMarker],
|
|
199
|
-
incarnation: process.env[incarnationMarker],
|
|
200
|
-
endpoint: server.baseUrl,
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
catch (error) {
|
|
204
|
-
await close();
|
|
205
|
-
throw error;
|
|
206
|
-
}
|
|
207
|
-
await activation;
|
|
208
|
-
await ManagedServerValues.send({
|
|
209
|
-
type: "ready",
|
|
210
|
-
slot: process.env[slotMarker],
|
|
211
|
-
incarnation: process.env[incarnationMarker],
|
|
212
|
-
endpoint: server.baseUrl,
|
|
213
|
-
});
|
|
214
|
-
return {
|
|
215
|
-
ready: true,
|
|
216
|
-
close,
|
|
217
|
-
};
|
|
109
|
+
return new ManagedChildLifecycle(server).start();
|
|
218
110
|
},
|
|
219
111
|
/**
|
|
220
112
|
* Starts a coordinator for the configured complete-replica cohort.
|
|
@@ -277,6 +169,174 @@ const ManagedServerValues = Object.freeze({
|
|
|
277
169
|
});
|
|
278
170
|
},
|
|
279
171
|
});
|
|
172
|
+
/**
|
|
173
|
+
* Runs the private child IPC lifecycle while keeping its listener open for relay cleanup.
|
|
174
|
+
*/
|
|
175
|
+
class ManagedChildLifecycle {
|
|
176
|
+
#activation = ManagedServerCoordinatorValues.deferred();
|
|
177
|
+
#serverCloseRelease = ManagedServerCoordinatorValues.deferred();
|
|
178
|
+
#server;
|
|
179
|
+
#activated = false;
|
|
180
|
+
#waitForParentClose = false;
|
|
181
|
+
#closing;
|
|
182
|
+
/**
|
|
183
|
+
* Creates the lifecycle around an assembled child server.
|
|
184
|
+
*
|
|
185
|
+
* @param server The already assembled and synchronized child server.
|
|
186
|
+
*/
|
|
187
|
+
constructor(server) {
|
|
188
|
+
this.#server = server;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
*
|
|
192
|
+
* Starts the child and waits for Coordinator activation.
|
|
193
|
+
*
|
|
194
|
+
* @returns A child handle after the private READY notification.
|
|
195
|
+
*/
|
|
196
|
+
async start() {
|
|
197
|
+
process.on("error", this.#containIpcError);
|
|
198
|
+
managedChildSubscriptionAccess.install((subscriptionId) => {
|
|
199
|
+
const notification = ManagedServerValues.send({
|
|
200
|
+
type: "subscription-installed",
|
|
201
|
+
slot: process.env[slotMarker],
|
|
202
|
+
incarnation: process.env[incarnationMarker],
|
|
203
|
+
subscriptionId,
|
|
204
|
+
});
|
|
205
|
+
// spine-log-boundary: server.managed_replica_subscription_ipc
|
|
206
|
+
void notification.catch(() => undefined);
|
|
207
|
+
});
|
|
208
|
+
process.on("message", this.#onMessage);
|
|
209
|
+
process.once("disconnect", this.#onDisconnect);
|
|
210
|
+
try {
|
|
211
|
+
await this.#notify("synchronizing", this.#server.baseUrl);
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
this.#serverCloseRelease.resolve(undefined);
|
|
215
|
+
await this.close();
|
|
216
|
+
throw error;
|
|
217
|
+
}
|
|
218
|
+
await this.#activation.promise;
|
|
219
|
+
await this.#notify("ready", this.#server.baseUrl);
|
|
220
|
+
return { ready: true, close: () => this.close() };
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
*
|
|
224
|
+
* Closes Delivery before closing the child listener and its server.
|
|
225
|
+
*
|
|
226
|
+
* @returns The shared close attempt; a failed attempt may be retried.
|
|
227
|
+
*/
|
|
228
|
+
close() {
|
|
229
|
+
this.#closing ??= this.#closeOnce().catch((error) => {
|
|
230
|
+
this.#closing = undefined;
|
|
231
|
+
throw error;
|
|
232
|
+
});
|
|
233
|
+
return this.#closing;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
*
|
|
237
|
+
* Performs child shutdown and observes a failed completion notification.
|
|
238
|
+
*
|
|
239
|
+
* @returns Completion after the child server closes.
|
|
240
|
+
*/
|
|
241
|
+
async #closeOnce() {
|
|
242
|
+
try {
|
|
243
|
+
await this.#drainDelivery();
|
|
244
|
+
await this.#closeServerAfterRelayCleanup();
|
|
245
|
+
// spine-log-boundary: server.managed_replica_closed_ipc
|
|
246
|
+
await this.#notify("closed").catch(() => undefined);
|
|
247
|
+
this.#disconnect();
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
// spine-log-boundary: server.managed_replica_close_failed_ipc
|
|
251
|
+
await this.#notify("close-failed").catch(() => undefined);
|
|
252
|
+
throw error;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
*
|
|
257
|
+
* Stops new child Delivery work and awaits admitted effects.
|
|
258
|
+
*
|
|
259
|
+
* @returns Completion when admitted work drains.
|
|
260
|
+
*/
|
|
261
|
+
async #drainDelivery() {
|
|
262
|
+
environmentDeliveryWorkerAccess.cancelManagedChild();
|
|
263
|
+
managedChildSubscriptionAccess.clear();
|
|
264
|
+
this.#activation.resolve(undefined);
|
|
265
|
+
// spine-log-boundary: server.managed_replica_drain_ipc
|
|
266
|
+
void this.#notify("draining").catch(() => undefined);
|
|
267
|
+
await runningServerAccess.drainDelivery(this.#server);
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
*
|
|
271
|
+
* Waits for parent relay cancellation before closing the child listener.
|
|
272
|
+
*
|
|
273
|
+
* @returns Completion after final server close.
|
|
274
|
+
*/
|
|
275
|
+
async #closeServerAfterRelayCleanup() {
|
|
276
|
+
// spine-log-boundary: server.managed_replica_delivery_drained_ipc
|
|
277
|
+
void this.#notify("delivery-drained").catch(() => undefined);
|
|
278
|
+
if (this.#waitForParentClose)
|
|
279
|
+
await this.#serverCloseRelease.promise;
|
|
280
|
+
await this.#server.close();
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
*
|
|
284
|
+
* Sends one child lifecycle notification on the private IPC channel.
|
|
285
|
+
*
|
|
286
|
+
* @param type The lifecycle phase to report.
|
|
287
|
+
* @param endpoint The listening endpoint for readiness messages.
|
|
288
|
+
* @returns Completion after the parent accepts the frame.
|
|
289
|
+
*/
|
|
290
|
+
#notify(type, endpoint) {
|
|
291
|
+
return ManagedServerValues.send({
|
|
292
|
+
type,
|
|
293
|
+
slot: process.env[slotMarker],
|
|
294
|
+
incarnation: process.env[incarnationMarker],
|
|
295
|
+
...(endpoint === undefined ? {} : { endpoint }),
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
*
|
|
300
|
+
* Releases child startup and close gates for matching parent messages.
|
|
301
|
+
*
|
|
302
|
+
* @param message The private parent frame.
|
|
303
|
+
*/
|
|
304
|
+
#onMessage = (message) => {
|
|
305
|
+
if (message.type === "activate" && !this.#activated) {
|
|
306
|
+
this.#activated = true;
|
|
307
|
+
environmentDeliveryWorkerAccess.activateManagedChild();
|
|
308
|
+
this.#activation.resolve(undefined);
|
|
309
|
+
}
|
|
310
|
+
else if (message.type === "close") {
|
|
311
|
+
this.#waitForParentClose = true;
|
|
312
|
+
ManagedServerValues.closeFromEvent(() => this.close());
|
|
313
|
+
}
|
|
314
|
+
else if (message.type === "finish-close") {
|
|
315
|
+
this.#serverCloseRelease.resolve(undefined);
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
/**
|
|
319
|
+
* Releases the final close phase if the parent disappears.
|
|
320
|
+
*/
|
|
321
|
+
#onDisconnect = () => {
|
|
322
|
+
this.#serverCloseRelease.resolve(undefined);
|
|
323
|
+
ManagedServerValues.closeFromEvent(() => this.close());
|
|
324
|
+
};
|
|
325
|
+
/**
|
|
326
|
+
* Contains IPC EPIPE after an already disappeared parent.
|
|
327
|
+
*/
|
|
328
|
+
#containIpcError = () => undefined;
|
|
329
|
+
/**
|
|
330
|
+
* Removes private listeners once the child server has closed.
|
|
331
|
+
*/
|
|
332
|
+
#disconnect() {
|
|
333
|
+
process.off("disconnect", this.#onDisconnect);
|
|
334
|
+
if (process.connected && typeof process.disconnect === "function")
|
|
335
|
+
process.disconnect();
|
|
336
|
+
process.off("error", this.#containIpcError);
|
|
337
|
+
process.off("message", this.#onMessage);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
280
340
|
/**
|
|
281
341
|
*
|
|
282
342
|
* Coordinates the parent-only lifecycle of managed application replicas.
|
|
@@ -288,6 +348,8 @@ export class ManagedServerCoordinator {
|
|
|
288
348
|
#restart;
|
|
289
349
|
#options;
|
|
290
350
|
#dependencies;
|
|
351
|
+
#terminalChildFailure;
|
|
352
|
+
#drainStart;
|
|
291
353
|
#closing = false;
|
|
292
354
|
#starts = 0;
|
|
293
355
|
#ready;
|
|
@@ -394,6 +456,16 @@ export class ManagedServerCoordinator {
|
|
|
394
456
|
this.#drainStarts();
|
|
395
457
|
return;
|
|
396
458
|
}
|
|
459
|
+
this.#installReplica(slot, incarnation, child);
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Records a spawned child and observes its private lifecycle messages.
|
|
463
|
+
*
|
|
464
|
+
* @param slot The configured replica slot.
|
|
465
|
+
* @param incarnation The current child incarnation identifier.
|
|
466
|
+
* @param child The spawned process handle.
|
|
467
|
+
*/
|
|
468
|
+
#installReplica(slot, incarnation, child) {
|
|
397
469
|
const replica = {
|
|
398
470
|
slot: slot.slot,
|
|
399
471
|
incarnation,
|
|
@@ -404,6 +476,9 @@ export class ManagedServerCoordinator {
|
|
|
404
476
|
draining: false,
|
|
405
477
|
subscriptionWaiters: new Map(),
|
|
406
478
|
closeAttempt: undefined,
|
|
479
|
+
closePromise: undefined,
|
|
480
|
+
closeFailure: undefined,
|
|
481
|
+
forcedFailure: undefined,
|
|
407
482
|
expectedExit: false,
|
|
408
483
|
terminal: false,
|
|
409
484
|
};
|
|
@@ -428,43 +503,86 @@ export class ManagedServerCoordinator {
|
|
|
428
503
|
* @param message The untrusted child IPC notification.
|
|
429
504
|
*/
|
|
430
505
|
#onMessage(slot, replica, message) {
|
|
506
|
+
if (this.#onCloseMessage(slot, replica, message))
|
|
507
|
+
return;
|
|
508
|
+
if (ManagedServerCoordinatorValues.subscriptionInstalledMessage(message, slot.slot, replica.incarnation)) {
|
|
509
|
+
if (slot.replica === replica)
|
|
510
|
+
this.#settleSubscriptionWaiter(replica, message.subscriptionId);
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
if (this.#onTransitionMessage(slot, replica, message))
|
|
514
|
+
return;
|
|
515
|
+
this.#onReadyMessage(slot, replica, message);
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Settles the private child shutdown notifications.
|
|
519
|
+
*
|
|
520
|
+
* @param slot The reported replica slot.
|
|
521
|
+
* @param replica The matching child incarnation.
|
|
522
|
+
* @param message The candidate notification.
|
|
523
|
+
* @returns Whether a close-phase notification was consumed.
|
|
524
|
+
*/
|
|
525
|
+
#onCloseMessage(slot, replica, message) {
|
|
431
526
|
const closeResult = ManagedServerCoordinatorValues.closeResultMessage(message, slot.slot, replica.incarnation);
|
|
432
527
|
if (closeResult !== undefined) {
|
|
433
528
|
if (slot.replica !== replica || !replica.draining || replica.closeAttempt === undefined)
|
|
434
|
-
return;
|
|
529
|
+
return true;
|
|
435
530
|
if (closeResult === "closed")
|
|
436
531
|
replica.closeAttempt.outcome.resolve(undefined);
|
|
437
|
-
else
|
|
532
|
+
else {
|
|
533
|
+
replica.closeAttempt.drained.reject(new Error("Managed child Delivery drain failed."));
|
|
438
534
|
replica.closeAttempt.outcome.reject(new Error("Managed child server close failed."));
|
|
439
|
-
|
|
535
|
+
}
|
|
536
|
+
return true;
|
|
440
537
|
}
|
|
441
|
-
if (ManagedServerCoordinatorValues.
|
|
442
|
-
if (
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
return;
|
|
538
|
+
if (ManagedServerCoordinatorValues.deliveryDrainedMessage(message, slot.slot, replica.incarnation)) {
|
|
539
|
+
if (replica.closeAttempt !== undefined)
|
|
540
|
+
replica.closeAttempt.drainedAcknowledged = true;
|
|
541
|
+
replica.closeAttempt?.drained.resolve(undefined);
|
|
542
|
+
return true;
|
|
446
543
|
}
|
|
544
|
+
return false;
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Advances a synchronizing or draining child without completing public readiness.
|
|
548
|
+
*
|
|
549
|
+
* @param slot The reported replica slot.
|
|
550
|
+
* @param replica The matching child incarnation.
|
|
551
|
+
* @param message The candidate notification.
|
|
552
|
+
* @returns Whether a transition notification was consumed.
|
|
553
|
+
*/
|
|
554
|
+
#onTransitionMessage(slot, replica, message) {
|
|
447
555
|
if (ManagedServerCoordinatorValues.synchronizingMessage(message, slot.slot, replica.incarnation)) {
|
|
448
556
|
if (slot.replica !== replica || replica.endpoint !== undefined)
|
|
449
|
-
return;
|
|
557
|
+
return true;
|
|
450
558
|
replica.endpoint = message.endpoint;
|
|
451
559
|
replica.synchronizing = true;
|
|
452
560
|
this.#notifyReadyMembers();
|
|
453
561
|
if (this.#nodeCoordinator === undefined)
|
|
454
562
|
replica.child.send({ type: "activate" }, () => undefined);
|
|
455
|
-
return;
|
|
563
|
+
return true;
|
|
456
564
|
}
|
|
457
565
|
if (ManagedServerCoordinatorValues.drainingMessage(message, slot.slot, replica.incarnation)) {
|
|
458
566
|
if (slot.replica !== replica)
|
|
459
|
-
return;
|
|
567
|
+
return true;
|
|
460
568
|
replica.closeAttempt?.acknowledged.resolve(undefined);
|
|
461
569
|
if (replica.draining)
|
|
462
|
-
return;
|
|
570
|
+
return true;
|
|
463
571
|
replica.draining = true;
|
|
464
572
|
replica.expectedExit = true;
|
|
465
573
|
this.#notifyReadyMembers();
|
|
466
|
-
return;
|
|
574
|
+
return true;
|
|
467
575
|
}
|
|
576
|
+
return false;
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Publishes one fully ready child after its synchronization phase.
|
|
580
|
+
*
|
|
581
|
+
* @param slot The reported replica slot.
|
|
582
|
+
* @param replica The matching child incarnation.
|
|
583
|
+
* @param message The candidate notification.
|
|
584
|
+
*/
|
|
585
|
+
#onReadyMessage(slot, replica, message) {
|
|
468
586
|
if (!ManagedServerCoordinatorValues.readyMessage(message, slot.slot, replica.incarnation))
|
|
469
587
|
return;
|
|
470
588
|
if (slot.replica !== replica || replica.readyAt !== undefined || this.#closing)
|
|
@@ -490,6 +608,9 @@ export class ManagedServerCoordinator {
|
|
|
490
608
|
if (slot.replica !== replica || replica.terminal)
|
|
491
609
|
return;
|
|
492
610
|
replica.terminal = true;
|
|
611
|
+
if (this.#closing && replica.closeFailure !== undefined)
|
|
612
|
+
this.#terminalChildFailure ??= replica.closeFailure;
|
|
613
|
+
this.#failIncompleteDrain(replica.closeAttempt);
|
|
493
614
|
this.#clearSubscriptionWaiters(replica, new Error("Managed child exited before subscription installed."));
|
|
494
615
|
slot.replica = undefined;
|
|
495
616
|
this.#notifyReadyMembers();
|
|
@@ -499,10 +620,10 @@ export class ManagedServerCoordinator {
|
|
|
499
620
|
}
|
|
500
621
|
if (this.#closing || replica.expectedExit)
|
|
501
622
|
return;
|
|
502
|
-
|
|
503
|
-
this.#dependencies.clock.now() - replica.readyAt >= this.#restart.healthyReadyMs
|
|
623
|
+
const healthy = replica.readyAt !== undefined &&
|
|
624
|
+
this.#dependencies.clock.now() - replica.readyAt >= this.#restart.healthyReadyMs;
|
|
625
|
+
if (healthy)
|
|
504
626
|
slot.failures = 0;
|
|
505
|
-
}
|
|
506
627
|
// spine-log-boundary: server.managed_replica_exit
|
|
507
628
|
emitServerWarning(lifecycleLogger, "Managed replica exited unexpectedly.", {
|
|
508
629
|
operation: "managed_replica.exit",
|
|
@@ -514,6 +635,20 @@ export class ManagedServerCoordinator {
|
|
|
514
635
|
this.#scheduleReplacement(slot);
|
|
515
636
|
this.#drainStarts();
|
|
516
637
|
}
|
|
638
|
+
/**
|
|
639
|
+
* Retains an exited child's missing drain acknowledgement across close attempts.
|
|
640
|
+
*
|
|
641
|
+
* @param closeAttempt The pending close attempt, if any.
|
|
642
|
+
*/
|
|
643
|
+
#failIncompleteDrain(closeAttempt) {
|
|
644
|
+
if (closeAttempt === undefined || closeAttempt.drainedAcknowledged)
|
|
645
|
+
return;
|
|
646
|
+
const failure = new Error("Managed child exited before Delivery drained.");
|
|
647
|
+
closeAttempt.exitFailure = failure;
|
|
648
|
+
if (this.#closing)
|
|
649
|
+
this.#terminalChildFailure ??= failure;
|
|
650
|
+
closeAttempt.drained.reject(failure);
|
|
651
|
+
}
|
|
517
652
|
/**
|
|
518
653
|
* Retires a child after a process error and starts bounded termination.
|
|
519
654
|
*
|
|
@@ -571,37 +706,7 @@ export class ManagedServerCoordinator {
|
|
|
571
706
|
const closing = this.#close;
|
|
572
707
|
if (closing !== undefined)
|
|
573
708
|
return closing;
|
|
574
|
-
const close = (
|
|
575
|
-
this.#closing = true;
|
|
576
|
-
this.#removeSignalHandlers();
|
|
577
|
-
for (const slot of this.#slots) {
|
|
578
|
-
if (slot.replacementTimer !== undefined)
|
|
579
|
-
this.#dependencies.clock.clearTimeout(slot.replacementTimer);
|
|
580
|
-
slot.replacementTimer = undefined;
|
|
581
|
-
}
|
|
582
|
-
// `beginDrain()` makes the unary snapshot empty synchronously. Start every
|
|
583
|
-
// child quiescence attempt without waiting for its reconciliation promise:
|
|
584
|
-
// child close IPC must be issued in this turn, while the Coordinator keeps
|
|
585
|
-
// subscription relays alive until every child has settled.
|
|
586
|
-
const draining = this.#nodeCoordinator?.beginDrain();
|
|
587
|
-
for (const slot of this.#slots)
|
|
588
|
-
if (slot.replica !== undefined)
|
|
589
|
-
this.#clearSubscriptionWaiters(slot.replica, new Error("Managed application is closing before subscription installed."));
|
|
590
|
-
const childCloses = [
|
|
591
|
-
...this.#slots.map((slot) => this.#closeReplica(slot.replica)),
|
|
592
|
-
...[...this.#retired].map((replica) => this.#closeRetired(replica)),
|
|
593
|
-
];
|
|
594
|
-
const results = await Promise.allSettled([draining, ...childCloses]);
|
|
595
|
-
const failures = [];
|
|
596
|
-
for (const result of results)
|
|
597
|
-
if (result.status === "rejected")
|
|
598
|
-
failures.push(result.reason);
|
|
599
|
-
if (failures.length === 1)
|
|
600
|
-
throw failures[0];
|
|
601
|
-
if (failures.length > 1)
|
|
602
|
-
throw new AggregateError(failures, "Managed child quiescence failed during drain.");
|
|
603
|
-
await this.#nodeCoordinator?.close();
|
|
604
|
-
})();
|
|
709
|
+
const close = this.#closeGracefully();
|
|
605
710
|
this.#close = close;
|
|
606
711
|
void close.then(undefined, () => {
|
|
607
712
|
if (this.#close === close)
|
|
@@ -609,6 +714,91 @@ export class ManagedServerCoordinator {
|
|
|
609
714
|
});
|
|
610
715
|
return close;
|
|
611
716
|
}
|
|
717
|
+
/**
|
|
718
|
+
* Stops replacement starts and awaits all cleanup phases.
|
|
719
|
+
*/
|
|
720
|
+
async #closeGracefully() {
|
|
721
|
+
this.#closing = true;
|
|
722
|
+
this.#removeSignalHandlers();
|
|
723
|
+
for (const slot of this.#slots) {
|
|
724
|
+
if (slot.replacementTimer !== undefined)
|
|
725
|
+
this.#dependencies.clock.clearTimeout(slot.replacementTimer);
|
|
726
|
+
slot.replacementTimer = undefined;
|
|
727
|
+
}
|
|
728
|
+
await this.#drainAndCloseReplicas();
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* Drains admitted child work, removes relays while listeners live, then closes children.
|
|
732
|
+
*
|
|
733
|
+
* @returns Completion after Coordinator and child cleanup attempts settle.
|
|
734
|
+
*/
|
|
735
|
+
async #drainAndCloseReplicas() {
|
|
736
|
+
this.#drainStart ??= this.#nodeCoordinator?.beginDrain() ?? Promise.resolve();
|
|
737
|
+
const closing = new Error("Managed application is closing before subscription installed.");
|
|
738
|
+
for (const slot of this.#slots)
|
|
739
|
+
if (slot.replica !== undefined)
|
|
740
|
+
this.#clearSubscriptionWaiters(slot.replica, closing);
|
|
741
|
+
const replicas = this.#slots.flatMap((slot) => slot.replica === undefined ? [] : [slot.replica]);
|
|
742
|
+
const childCloses = [
|
|
743
|
+
...replicas.map((replica) => this.#closeReplica(replica)),
|
|
744
|
+
...[...this.#retired].map((replica) => this.#closeRetired(replica)),
|
|
745
|
+
];
|
|
746
|
+
const children = Promise.allSettled(childCloses);
|
|
747
|
+
const failures = await this.#awaitDeliveryDrains(replicas);
|
|
748
|
+
await this.#finishClose(replicas, children, failures);
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* Cancels relays, releases children, and closes the Coordinator after a drain attempt.
|
|
752
|
+
* @param replicas The active children being closed.
|
|
753
|
+
* @param children The already-running bounded child close attempts.
|
|
754
|
+
* @param failures Errors collected from the drain phase.
|
|
755
|
+
*/
|
|
756
|
+
async #finishClose(replicas, children, failures) {
|
|
757
|
+
try {
|
|
758
|
+
await this.#nodeCoordinator?.closeSubscriptions();
|
|
759
|
+
}
|
|
760
|
+
catch (error) {
|
|
761
|
+
failures.push(error);
|
|
762
|
+
}
|
|
763
|
+
for (const replica of replicas) {
|
|
764
|
+
try {
|
|
765
|
+
if (replica.child.connected)
|
|
766
|
+
replica.child.send({ type: "finish-close" }, () => undefined);
|
|
767
|
+
}
|
|
768
|
+
catch (error) {
|
|
769
|
+
failures.push(error);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
const childResults = await children;
|
|
773
|
+
const coordinator = Promise.resolve().then(() => this.#nodeCoordinator?.close());
|
|
774
|
+
const [coordinatorResult] = await Promise.allSettled([coordinator]);
|
|
775
|
+
for (const result of childResults)
|
|
776
|
+
if (result.status === "rejected" && !failures.includes(result.reason))
|
|
777
|
+
failures.push(result.reason);
|
|
778
|
+
if (coordinatorResult.status === "rejected")
|
|
779
|
+
failures.push(coordinatorResult.reason);
|
|
780
|
+
if (this.#terminalChildFailure !== undefined && !failures.includes(this.#terminalChildFailure))
|
|
781
|
+
failures.push(this.#terminalChildFailure);
|
|
782
|
+
if (failures.length === 1)
|
|
783
|
+
throw failures[0];
|
|
784
|
+
if (failures.length > 1)
|
|
785
|
+
throw new AggregateError(failures, "Managed child quiescence failed during drain.");
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Waits for actual child Delivery drains without awaiting final child exits.
|
|
789
|
+
*
|
|
790
|
+
* @param replicas The active child incarnations.
|
|
791
|
+
* @returns Drain failures to report after child and Coordinator cleanup.
|
|
792
|
+
*/
|
|
793
|
+
async #awaitDeliveryDrains(replicas) {
|
|
794
|
+
const deliveryDrains = replicas.map((replica) => replica.closeAttempt?.drained.promise ?? Promise.resolve());
|
|
795
|
+
const results = await Promise.allSettled([this.#drainStart, ...deliveryDrains]);
|
|
796
|
+
const failures = [];
|
|
797
|
+
for (const result of results)
|
|
798
|
+
if (result.status === "rejected")
|
|
799
|
+
failures.push(result.reason);
|
|
800
|
+
return failures;
|
|
801
|
+
}
|
|
612
802
|
/**
|
|
613
803
|
* Closes the current replica through the bounded child shutdown procedure.
|
|
614
804
|
*
|
|
@@ -616,7 +806,28 @@ export class ManagedServerCoordinator {
|
|
|
616
806
|
* @returns Completion after child shutdown.
|
|
617
807
|
*/
|
|
618
808
|
#closeReplica(replica) {
|
|
619
|
-
|
|
809
|
+
if (replica === undefined)
|
|
810
|
+
return Promise.resolve();
|
|
811
|
+
if (replica.closePromise !== undefined)
|
|
812
|
+
return replica.closePromise;
|
|
813
|
+
replica.closeFailure = undefined;
|
|
814
|
+
const close = ManagedServerCoordinatorValues.close(replica, this.#dependencies.clock);
|
|
815
|
+
replica.closePromise = close;
|
|
816
|
+
void close.then(() => {
|
|
817
|
+
replica.closeFailure = undefined;
|
|
818
|
+
}, (error) => {
|
|
819
|
+
replica.closeFailure = error;
|
|
820
|
+
if (replica.forcedFailure !== undefined || replica.terminal)
|
|
821
|
+
this.#terminalChildFailure ??= replica.forcedFailure ?? error;
|
|
822
|
+
});
|
|
823
|
+
// spine-log-boundary: server.managed_replica_close_finally_observer
|
|
824
|
+
void close
|
|
825
|
+
.finally(() => {
|
|
826
|
+
if (replica.closePromise === close)
|
|
827
|
+
replica.closePromise = undefined;
|
|
828
|
+
})
|
|
829
|
+
.catch(() => undefined);
|
|
830
|
+
return close;
|
|
620
831
|
}
|
|
621
832
|
/**
|
|
622
833
|
* Terminates a retired replica and removes it from the pending set.
|
|
@@ -928,6 +1139,8 @@ const ManagedServerCoordinatorValues = Object.freeze({
|
|
|
928
1139
|
resolve = next;
|
|
929
1140
|
reject = fail;
|
|
930
1141
|
});
|
|
1142
|
+
// spine-log-boundary: server.managed_deferred_rejection_observer
|
|
1143
|
+
void promise.catch(() => undefined);
|
|
931
1144
|
return { promise, resolve, reject };
|
|
932
1145
|
},
|
|
933
1146
|
/**
|
|
@@ -943,7 +1156,7 @@ const ManagedServerCoordinatorValues = Object.freeze({
|
|
|
943
1156
|
},
|
|
944
1157
|
dependencies: {
|
|
945
1158
|
clock: {
|
|
946
|
-
now: () => Time.
|
|
1159
|
+
now: () => Time.monotonicTime(),
|
|
947
1160
|
setTimeout: (onTimeout, delay) => setTimeout(onTimeout, delay),
|
|
948
1161
|
clearTimeout: (timer) => {
|
|
949
1162
|
clearTimeout(timer);
|
|
@@ -1053,6 +1266,22 @@ const ManagedServerCoordinatorValues = Object.freeze({
|
|
|
1053
1266
|
candidate.slot === String(slot) &&
|
|
1054
1267
|
candidate.incarnation === incarnation);
|
|
1055
1268
|
},
|
|
1269
|
+
/**
|
|
1270
|
+
* Matches the child's completed Delivery drain before its listener closes.
|
|
1271
|
+
*
|
|
1272
|
+
* @param message The candidate child IPC notification.
|
|
1273
|
+
* @param slot The expected replica slot.
|
|
1274
|
+
* @param incarnation The expected child incarnation token.
|
|
1275
|
+
* @returns Whether the notification belongs to that child.
|
|
1276
|
+
*/
|
|
1277
|
+
deliveryDrainedMessage(message, slot, incarnation) {
|
|
1278
|
+
if (typeof message !== "object" || message === null)
|
|
1279
|
+
return false;
|
|
1280
|
+
const candidate = message;
|
|
1281
|
+
return (candidate.type === "delivery-drained" &&
|
|
1282
|
+
candidate.slot === String(slot) &&
|
|
1283
|
+
candidate.incarnation === incarnation);
|
|
1284
|
+
},
|
|
1056
1285
|
/**
|
|
1057
1286
|
* Matches a CLOSED or CLOSE-FAILED notification from the expected child.
|
|
1058
1287
|
*
|
|
@@ -1090,35 +1319,93 @@ const ManagedServerCoordinatorValues = Object.freeze({
|
|
|
1090
1319
|
}));
|
|
1091
1320
|
const closeAttempt = {
|
|
1092
1321
|
acknowledged: ManagedServerCoordinatorValues.deferred(),
|
|
1322
|
+
drained: ManagedServerCoordinatorValues.deferred(),
|
|
1093
1323
|
outcome: ManagedServerCoordinatorValues.deferred(),
|
|
1094
1324
|
};
|
|
1095
1325
|
replica.closeAttempt = closeAttempt;
|
|
1096
1326
|
try {
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
]);
|
|
1103
|
-
if (await ManagedServerCoordinatorValues.within(observation, closeGraceMs, clock)) {
|
|
1104
|
-
if ((await observation) === "exited")
|
|
1105
|
-
return;
|
|
1106
|
-
await Promise.race([
|
|
1107
|
-
closeAttempt.outcome.promise,
|
|
1108
|
-
exited.then(() => {
|
|
1109
|
-
throw new Error("Managed child exited before graceful close completed.");
|
|
1110
|
-
}),
|
|
1111
|
-
]);
|
|
1112
|
-
await ManagedServerCoordinatorValues.terminate(child, clock, exited);
|
|
1113
|
-
return;
|
|
1114
|
-
}
|
|
1115
|
-
await ManagedServerCoordinatorValues.terminate(child, clock, exited, false);
|
|
1327
|
+
await ManagedServerCoordinatorValues.awaitChildClose(child, closeAttempt, exited, clock);
|
|
1328
|
+
}
|
|
1329
|
+
catch (error) {
|
|
1330
|
+
replica.forcedFailure = closeAttempt.forcedFailure;
|
|
1331
|
+
throw error;
|
|
1116
1332
|
}
|
|
1117
1333
|
finally {
|
|
1118
1334
|
if (replica.closeAttempt === closeAttempt)
|
|
1119
1335
|
replica.closeAttempt = undefined;
|
|
1120
1336
|
}
|
|
1121
1337
|
},
|
|
1338
|
+
/**
|
|
1339
|
+
* Waits for child drain and final close, escalating only after bounded acknowledgement.
|
|
1340
|
+
*
|
|
1341
|
+
* @param child The child process to close.
|
|
1342
|
+
* @param closeAttempt Tracks the child's IPC phase acknowledgements.
|
|
1343
|
+
* @param exited Resolves when the child exits.
|
|
1344
|
+
* @param clock Supplies bounded shutdown timers.
|
|
1345
|
+
* @returns Completion after the child has exited or close failed.
|
|
1346
|
+
*/
|
|
1347
|
+
async awaitChildClose(child, closeAttempt, exited, clock) {
|
|
1348
|
+
if (child.connected)
|
|
1349
|
+
child.send({ type: "close" }, () => undefined);
|
|
1350
|
+
const observation = Promise.race([
|
|
1351
|
+
closeAttempt.acknowledged.promise.then(() => "acknowledged"),
|
|
1352
|
+
exited.then(() => "exited"),
|
|
1353
|
+
]);
|
|
1354
|
+
if (!(await ManagedServerCoordinatorValues.within(observation, closeGraceMs, clock))) {
|
|
1355
|
+
await this.missingDrainAck(child, closeAttempt, exited, clock);
|
|
1356
|
+
}
|
|
1357
|
+
if ((await observation) === "exited") {
|
|
1358
|
+
this.verifyExitedDrain(closeAttempt);
|
|
1359
|
+
return;
|
|
1360
|
+
}
|
|
1361
|
+
await ManagedServerCoordinatorValues.awaitChildOutcome(closeAttempt, exited);
|
|
1362
|
+
await ManagedServerCoordinatorValues.terminate(child, clock, exited);
|
|
1363
|
+
},
|
|
1364
|
+
/**
|
|
1365
|
+
* Rejects an unacknowledged Delivery drain after bounded child termination.
|
|
1366
|
+
*
|
|
1367
|
+
* @param child The unresponsive child process.
|
|
1368
|
+
* @param closeAttempt The current close attempt.
|
|
1369
|
+
* @param exited Resolves when the child exits.
|
|
1370
|
+
* @param clock Supplies bounded shutdown timers.
|
|
1371
|
+
* @returns No value; the missing acknowledgement always rejects.
|
|
1372
|
+
*/
|
|
1373
|
+
async missingDrainAck(child, closeAttempt, exited, clock) {
|
|
1374
|
+
const failure = new Error("Managed child did not acknowledge Delivery drain.");
|
|
1375
|
+
closeAttempt.forcedFailure = failure;
|
|
1376
|
+
closeAttempt.drained.reject(failure);
|
|
1377
|
+
try {
|
|
1378
|
+
await this.terminate(child, clock, exited, false);
|
|
1379
|
+
}
|
|
1380
|
+
catch (termination) {
|
|
1381
|
+
throw new AggregateError([failure, termination], "Managed child shutdown failed.");
|
|
1382
|
+
}
|
|
1383
|
+
throw failure;
|
|
1384
|
+
},
|
|
1385
|
+
/**
|
|
1386
|
+
* Rejects a child exit that preceded its Delivery drain acknowledgement.
|
|
1387
|
+
*
|
|
1388
|
+
* @param closeAttempt The observed close attempt.
|
|
1389
|
+
*/
|
|
1390
|
+
verifyExitedDrain(closeAttempt) {
|
|
1391
|
+
if (closeAttempt.exitFailure !== undefined)
|
|
1392
|
+
throw closeAttempt.exitFailure;
|
|
1393
|
+
},
|
|
1394
|
+
/**
|
|
1395
|
+
* Waits for the real child close outcome before process reaping.
|
|
1396
|
+
*
|
|
1397
|
+
* @param closeAttempt Tracks the child's close notifications.
|
|
1398
|
+
* @param exited Resolves when the child process exits.
|
|
1399
|
+
* @returns Completion after the child's final close fact.
|
|
1400
|
+
*/
|
|
1401
|
+
async awaitChildOutcome(closeAttempt, exited) {
|
|
1402
|
+
await Promise.race([
|
|
1403
|
+
closeAttempt.outcome.promise,
|
|
1404
|
+
exited.then(() => {
|
|
1405
|
+
throw new Error("Managed child exited before graceful close completed.");
|
|
1406
|
+
}),
|
|
1407
|
+
]);
|
|
1408
|
+
},
|
|
1122
1409
|
/**
|
|
1123
1410
|
* Waits for child exit, then escalates through SIGTERM and SIGKILL within bounded intervals.
|
|
1124
1411
|
*
|
|
@@ -1161,6 +1448,9 @@ const ManagedServerCoordinatorValues = Object.freeze({
|
|
|
1161
1448
|
void promise.then(() => {
|
|
1162
1449
|
clock.clearTimeout(timer);
|
|
1163
1450
|
resolve(true);
|
|
1451
|
+
}, () => {
|
|
1452
|
+
clock.clearTimeout(timer);
|
|
1453
|
+
resolve(true);
|
|
1164
1454
|
});
|
|
1165
1455
|
});
|
|
1166
1456
|
},
|