@spine-event-engine/server 2.0.0-snapshot.2
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/LICENSE +201 -0
- package/README.md +569 -0
- package/REFERENCE.md +488 -0
- package/dist/bus/command-bus.d.ts +61 -0
- package/dist/bus/command-bus.d.ts.map +1 -0
- package/dist/bus/command-bus.js +201 -0
- package/dist/bus/command-bus.js.map +1 -0
- package/dist/bus/command-dispatcher-registry.d.ts +33 -0
- package/dist/bus/command-dispatcher-registry.d.ts.map +1 -0
- package/dist/bus/command-dispatcher-registry.js +75 -0
- package/dist/bus/command-dispatcher-registry.js.map +1 -0
- package/dist/bus/command-dispatcher.d.ts +25 -0
- package/dist/bus/command-dispatcher.d.ts.map +1 -0
- package/dist/bus/command-dispatcher.js +15 -0
- package/dist/bus/command-dispatcher.js.map +1 -0
- package/dist/bus/command-errors.d.ts +23 -0
- package/dist/bus/command-errors.d.ts.map +1 -0
- package/dist/bus/command-errors.js +52 -0
- package/dist/bus/command-errors.js.map +1 -0
- package/dist/bus/event-bus.d.ts +113 -0
- package/dist/bus/event-bus.d.ts.map +1 -0
- package/dist/bus/event-bus.js +518 -0
- package/dist/bus/event-bus.js.map +1 -0
- package/dist/bus/event-dispatcher-origin-schemas.d.ts +17 -0
- package/dist/bus/event-dispatcher-origin-schemas.d.ts.map +1 -0
- package/dist/bus/event-dispatcher-origin-schemas.js +32 -0
- package/dist/bus/event-dispatcher-origin-schemas.js.map +1 -0
- package/dist/bus/event-dispatcher-registry.d.ts +48 -0
- package/dist/bus/event-dispatcher-registry.d.ts.map +1 -0
- package/dist/bus/event-dispatcher-registry.js +176 -0
- package/dist/bus/event-dispatcher-registry.js.map +1 -0
- package/dist/bus/event-dispatcher.d.ts +48 -0
- package/dist/bus/event-dispatcher.d.ts.map +1 -0
- package/dist/bus/event-dispatcher.js +15 -0
- package/dist/bus/event-dispatcher.js.map +1 -0
- package/dist/context/bounded-context.d.ts +606 -0
- package/dist/context/bounded-context.d.ts.map +1 -0
- package/dist/context/bounded-context.js +1780 -0
- package/dist/context/bounded-context.js.map +1 -0
- package/dist/context/entity-inbox.d.ts +63 -0
- package/dist/context/entity-inbox.d.ts.map +1 -0
- package/dist/context/entity-inbox.js +380 -0
- package/dist/context/entity-inbox.js.map +1 -0
- package/dist/context/local-inbox-handoff.d.ts +244 -0
- package/dist/context/local-inbox-handoff.d.ts.map +1 -0
- package/dist/context/local-inbox-handoff.js +334 -0
- package/dist/context/local-inbox-handoff.js.map +1 -0
- package/dist/context/projection-handoff.d.ts +46 -0
- package/dist/context/projection-handoff.d.ts.map +1 -0
- package/dist/context/projection-handoff.js +172 -0
- package/dist/context/projection-handoff.js.map +1 -0
- package/dist/context/tenant-index.d.ts +41 -0
- package/dist/context/tenant-index.d.ts.map +1 -0
- package/dist/context/tenant-index.js +92 -0
- package/dist/context/tenant-index.js.map +1 -0
- package/dist/delivery/conditional-pickup.d.ts +25 -0
- package/dist/delivery/conditional-pickup.d.ts.map +1 -0
- package/dist/delivery/conditional-pickup.js +26 -0
- package/dist/delivery/conditional-pickup.js.map +1 -0
- package/dist/delivery/delivery-builder.d.ts +216 -0
- package/dist/delivery/delivery-builder.d.ts.map +1 -0
- package/dist/delivery/delivery-builder.js +323 -0
- package/dist/delivery/delivery-builder.js.map +1 -0
- package/dist/delivery/delivery-lease.d.ts +15 -0
- package/dist/delivery/delivery-lease.d.ts.map +1 -0
- package/dist/delivery/delivery-lease.js +44 -0
- package/dist/delivery/delivery-lease.js.map +1 -0
- package/dist/delivery/delivery-loop.d.ts +113 -0
- package/dist/delivery/delivery-loop.d.ts.map +1 -0
- package/dist/delivery/delivery-loop.js +119 -0
- package/dist/delivery/delivery-loop.js.map +1 -0
- package/dist/delivery/delivery-monitor.d.ts +165 -0
- package/dist/delivery/delivery-monitor.d.ts.map +1 -0
- package/dist/delivery/delivery-monitor.js +168 -0
- package/dist/delivery/delivery-monitor.js.map +1 -0
- package/dist/delivery/delivery-ports.d.ts +141 -0
- package/dist/delivery/delivery-ports.d.ts.map +1 -0
- package/dist/delivery/delivery-ports.js +15 -0
- package/dist/delivery/delivery-ports.js.map +1 -0
- package/dist/delivery/delivery-run-control.d.ts +50 -0
- package/dist/delivery/delivery-run-control.d.ts.map +1 -0
- package/dist/delivery/delivery-run-control.js +66 -0
- package/dist/delivery/delivery-run-control.js.map +1 -0
- package/dist/delivery/delivery-run-coordinator.d.ts +218 -0
- package/dist/delivery/delivery-run-coordinator.d.ts.map +1 -0
- package/dist/delivery/delivery-run-coordinator.js +609 -0
- package/dist/delivery/delivery-run-coordinator.js.map +1 -0
- package/dist/delivery/delivery-storage-error.d.ts +13 -0
- package/dist/delivery/delivery-storage-error.d.ts.map +1 -0
- package/dist/delivery/delivery-storage-error.js +30 -0
- package/dist/delivery/delivery-storage-error.js.map +1 -0
- package/dist/delivery/delivery-supervisor.d.ts +175 -0
- package/dist/delivery/delivery-supervisor.d.ts.map +1 -0
- package/dist/delivery/delivery-supervisor.js +469 -0
- package/dist/delivery/delivery-supervisor.js.map +1 -0
- package/dist/delivery/delivery-worker.d.ts +180 -0
- package/dist/delivery/delivery-worker.d.ts.map +1 -0
- package/dist/delivery/delivery-worker.js +232 -0
- package/dist/delivery/delivery-worker.js.map +1 -0
- package/dist/delivery/delivery.d.ts +238 -0
- package/dist/delivery/delivery.d.ts.map +1 -0
- package/dist/delivery/delivery.js +400 -0
- package/dist/delivery/delivery.js.map +1 -0
- package/dist/delivery/inbox-records.d.ts +19 -0
- package/dist/delivery/inbox-records.d.ts.map +1 -0
- package/dist/delivery/inbox-records.js +291 -0
- package/dist/delivery/inbox-records.js.map +1 -0
- package/dist/delivery/inbox-storage.d.ts +81 -0
- package/dist/delivery/inbox-storage.d.ts.map +1 -0
- package/dist/delivery/inbox-storage.js +353 -0
- package/dist/delivery/inbox-storage.js.map +1 -0
- package/dist/delivery/inbox.d.ts +263 -0
- package/dist/delivery/inbox.d.ts.map +1 -0
- package/dist/delivery/inbox.js +172 -0
- package/dist/delivery/inbox.js.map +1 -0
- package/dist/delivery/parked-delivery-obligations.d.ts +182 -0
- package/dist/delivery/parked-delivery-obligations.d.ts.map +1 -0
- package/dist/delivery/parked-delivery-obligations.js +390 -0
- package/dist/delivery/parked-delivery-obligations.js.map +1 -0
- package/dist/delivery/shard-index.d.ts +41 -0
- package/dist/delivery/shard-index.d.ts.map +1 -0
- package/dist/delivery/shard-index.js +67 -0
- package/dist/delivery/shard-index.js.map +1 -0
- package/dist/delivery/sharded-work-registry.d.ts +114 -0
- package/dist/delivery/sharded-work-registry.d.ts.map +1 -0
- package/dist/delivery/sharded-work-registry.js +360 -0
- package/dist/delivery/sharded-work-registry.js.map +1 -0
- package/dist/entity/entity-metadata.d.ts +173 -0
- package/dist/entity/entity-metadata.d.ts.map +1 -0
- package/dist/entity/entity-metadata.js +187 -0
- package/dist/entity/entity-metadata.js.map +1 -0
- package/dist/entity/entity-storage-descriptor.d.ts +64 -0
- package/dist/entity/entity-storage-descriptor.d.ts.map +1 -0
- package/dist/entity/entity-storage-descriptor.js +227 -0
- package/dist/entity/entity-storage-descriptor.js.map +1 -0
- package/dist/entity/entity-transaction.d.ts +372 -0
- package/dist/entity/entity-transaction.d.ts.map +1 -0
- package/dist/entity/entity-transaction.js +413 -0
- package/dist/entity/entity-transaction.js.map +1 -0
- package/dist/entity/entity-transition-validation.d.ts +47 -0
- package/dist/entity/entity-transition-validation.d.ts.map +1 -0
- package/dist/entity/entity-transition-validation.js +327 -0
- package/dist/entity/entity-transition-validation.js.map +1 -0
- package/dist/entity/entity.d.ts +499 -0
- package/dist/entity/entity.d.ts.map +1 -0
- package/dist/entity/entity.js +829 -0
- package/dist/entity/entity.js.map +1 -0
- package/dist/entity/implicit-required-id.d.ts +15 -0
- package/dist/entity/implicit-required-id.d.ts.map +1 -0
- package/dist/entity/implicit-required-id.js +123 -0
- package/dist/entity/implicit-required-id.js.map +1 -0
- package/dist/entity/spec-scanner.d.ts +14 -0
- package/dist/entity/spec-scanner.d.ts.map +1 -0
- package/dist/entity/spec-scanner.js +37 -0
- package/dist/entity/spec-scanner.js.map +1 -0
- package/dist/handler/build-time-handler-analyzer.d.ts +150 -0
- package/dist/handler/build-time-handler-analyzer.d.ts.map +1 -0
- package/dist/handler/build-time-handler-analyzer.js +1101 -0
- package/dist/handler/build-time-handler-analyzer.js.map +1 -0
- package/dist/handler/command-registration-readiness.d.ts +99 -0
- package/dist/handler/command-registration-readiness.d.ts.map +1 -0
- package/dist/handler/command-registration-readiness.js +118 -0
- package/dist/handler/command-registration-readiness.js.map +1 -0
- package/dist/handler/event-handler-filter.d.ts +43 -0
- package/dist/handler/event-handler-filter.d.ts.map +1 -0
- package/dist/handler/event-handler-filter.js +128 -0
- package/dist/handler/event-handler-filter.js.map +1 -0
- package/dist/handler/event-registration-readiness.d.ts +196 -0
- package/dist/handler/event-registration-readiness.d.ts.map +1 -0
- package/dist/handler/event-registration-readiness.js +187 -0
- package/dist/handler/event-registration-readiness.js.map +1 -0
- package/dist/handler/external.d.ts +10 -0
- package/dist/handler/external.d.ts.map +1 -0
- package/dist/handler/external.js +15 -0
- package/dist/handler/external.js.map +1 -0
- package/dist/handler/generated-handler-registry.d.ts +154 -0
- package/dist/handler/generated-handler-registry.d.ts.map +1 -0
- package/dist/handler/generated-handler-registry.js +223 -0
- package/dist/handler/generated-handler-registry.js.map +1 -0
- package/dist/handler/generated-registry-discovery.d.ts +94 -0
- package/dist/handler/generated-registry-discovery.d.ts.map +1 -0
- package/dist/handler/generated-registry-discovery.js +228 -0
- package/dist/handler/generated-registry-discovery.js.map +1 -0
- package/dist/handler/generated-registry-writer.d.ts +58 -0
- package/dist/handler/generated-registry-writer.d.ts.map +1 -0
- package/dist/handler/generated-registry-writer.js +458 -0
- package/dist/handler/generated-registry-writer.js.map +1 -0
- package/dist/handler/handler-codegen.d.ts +48 -0
- package/dist/handler/handler-codegen.d.ts.map +1 -0
- package/dist/handler/handler-codegen.js +214 -0
- package/dist/handler/handler-codegen.js.map +1 -0
- package/dist/handler/handler-decorators.d.ts +134 -0
- package/dist/handler/handler-decorators.d.ts.map +1 -0
- package/dist/handler/handler-decorators.js +239 -0
- package/dist/handler/handler-decorators.js.map +1 -0
- package/dist/handler/handler-metadata.d.ts +510 -0
- package/dist/handler/handler-metadata.d.ts.map +1 -0
- package/dist/handler/handler-metadata.js +391 -0
- package/dist/handler/handler-metadata.js.map +1 -0
- package/dist/handler/registration-readiness-metadata.d.ts +68 -0
- package/dist/handler/registration-readiness-metadata.d.ts.map +1 -0
- package/dist/handler/registration-readiness-metadata.js +170 -0
- package/dist/handler/registration-readiness-metadata.js.map +1 -0
- package/dist/handler/rejection-source.d.ts +11 -0
- package/dist/handler/rejection-source.d.ts.map +1 -0
- package/dist/handler/rejection-source.js +25 -0
- package/dist/handler/rejection-source.js.map +1 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +54 -0
- package/dist/index.js.map +1 -0
- package/dist/integration/external-messages.d.ts +49 -0
- package/dist/integration/external-messages.d.ts.map +1 -0
- package/dist/integration/external-messages.js +112 -0
- package/dist/integration/external-messages.js.map +1 -0
- package/dist/integration/integration-broker.d.ts +74 -0
- package/dist/integration/integration-broker.d.ts.map +1 -0
- package/dist/integration/integration-broker.js +444 -0
- package/dist/integration/integration-broker.js.map +1 -0
- package/dist/integration/third-party-context.d.ts +56 -0
- package/dist/integration/third-party-context.d.ts.map +1 -0
- package/dist/integration/third-party-context.js +130 -0
- package/dist/integration/third-party-context.js.map +1 -0
- package/dist/repository/command-errors.d.ts +25 -0
- package/dist/repository/command-errors.d.ts.map +1 -0
- package/dist/repository/command-errors.js +43 -0
- package/dist/repository/command-errors.js.map +1 -0
- package/dist/repository/command-routing.d.ts +72 -0
- package/dist/repository/command-routing.d.ts.map +1 -0
- package/dist/repository/command-routing.js +98 -0
- package/dist/repository/command-routing.js.map +1 -0
- package/dist/repository/commit-fence.d.ts +22 -0
- package/dist/repository/commit-fence.d.ts.map +1 -0
- package/dist/repository/commit-fence.js +37 -0
- package/dist/repository/commit-fence.js.map +1 -0
- package/dist/repository/event-routing.d.ts +71 -0
- package/dist/repository/event-routing.d.ts.map +1 -0
- package/dist/repository/event-routing.js +96 -0
- package/dist/repository/event-routing.js.map +1 -0
- package/dist/repository/primitive-id.d.ts +86 -0
- package/dist/repository/primitive-id.d.ts.map +1 -0
- package/dist/repository/primitive-id.js +110 -0
- package/dist/repository/primitive-id.js.map +1 -0
- package/dist/repository/repository.d.ts +670 -0
- package/dist/repository/repository.d.ts.map +1 -0
- package/dist/repository/repository.js +3298 -0
- package/dist/repository/repository.js.map +1 -0
- package/dist/repository/routing-declarations.d.ts +75 -0
- package/dist/repository/routing-declarations.d.ts.map +1 -0
- package/dist/repository/routing-declarations.js +101 -0
- package/dist/repository/routing-declarations.js.map +1 -0
- package/dist/repository/state-update-routing.d.ts +66 -0
- package/dist/repository/state-update-routing.d.ts.map +1 -0
- package/dist/repository/state-update-routing.js +91 -0
- package/dist/repository/state-update-routing.js.map +1 -0
- package/dist/runtime/runtime.d.ts +156 -0
- package/dist/runtime/runtime.d.ts.map +1 -0
- package/dist/runtime/runtime.js +215 -0
- package/dist/runtime/runtime.js.map +1 -0
- package/dist/runtime/signal-intake.d.ts +84 -0
- package/dist/runtime/signal-intake.d.ts.map +1 -0
- package/dist/runtime/signal-intake.js +94 -0
- package/dist/runtime/signal-intake.js.map +1 -0
- package/dist/runtime/signal-metadata.d.ts +242 -0
- package/dist/runtime/signal-metadata.d.ts.map +1 -0
- package/dist/runtime/signal-metadata.js +368 -0
- package/dist/runtime/signal-metadata.js.map +1 -0
- package/dist/server/browser-server.d.ts +49 -0
- package/dist/server/browser-server.d.ts.map +1 -0
- package/dist/server/browser-server.js +633 -0
- package/dist/server/browser-server.js.map +1 -0
- package/dist/server/durable-subscription-bindings.d.ts +122 -0
- package/dist/server/durable-subscription-bindings.d.ts.map +1 -0
- package/dist/server/durable-subscription-bindings.js +524 -0
- package/dist/server/durable-subscription-bindings.js.map +1 -0
- package/dist/server/environment-attachment.d.ts +356 -0
- package/dist/server/environment-attachment.d.ts.map +1 -0
- package/dist/server/environment-attachment.js +2061 -0
- package/dist/server/environment-attachment.js.map +1 -0
- package/dist/server/environment-delivery-records.d.ts +77 -0
- package/dist/server/environment-delivery-records.d.ts.map +1 -0
- package/dist/server/environment-delivery-records.js +282 -0
- package/dist/server/environment-delivery-records.js.map +1 -0
- package/dist/server/environment-delivery-worker.d.ts +161 -0
- package/dist/server/environment-delivery-worker.d.ts.map +1 -0
- package/dist/server/environment-delivery-worker.js +664 -0
- package/dist/server/environment-delivery-worker.js.map +1 -0
- package/dist/server/environment.d.ts +43 -0
- package/dist/server/environment.d.ts.map +1 -0
- package/dist/server/environment.js +90 -0
- package/dist/server/environment.js.map +1 -0
- package/dist/server/managed-child-subscription.d.ts +11 -0
- package/dist/server/managed-child-subscription.d.ts.map +1 -0
- package/dist/server/managed-child-subscription.js +44 -0
- package/dist/server/managed-child-subscription.js.map +1 -0
- package/dist/server/managed-server-application.d.ts +324 -0
- package/dist/server/managed-server-application.d.ts.map +1 -0
- package/dist/server/managed-server-application.js +937 -0
- package/dist/server/managed-server-application.js.map +1 -0
- package/dist/server/node-coordinator.d.ts +191 -0
- package/dist/server/node-coordinator.d.ts.map +1 -0
- package/dist/server/node-coordinator.js +552 -0
- package/dist/server/node-coordinator.js.map +1 -0
- package/dist/server/process-server-coordinator.d.ts +15 -0
- package/dist/server/process-server-coordinator.d.ts.map +1 -0
- package/dist/server/process-server-coordinator.js +112 -0
- package/dist/server/process-server-coordinator.js.map +1 -0
- package/dist/server/reserved-spine-rpc-paths.json +8 -0
- package/dist/server/retryable-close.d.ts +28 -0
- package/dist/server/retryable-close.d.ts.map +1 -0
- package/dist/server/retryable-close.js +129 -0
- package/dist/server/retryable-close.js.map +1 -0
- package/dist/server/server-environment.d.ts +180 -0
- package/dist/server/server-environment.d.ts.map +1 -0
- package/dist/server/server-environment.js +418 -0
- package/dist/server/server-environment.js.map +1 -0
- package/dist/server/server-log.d.ts +18 -0
- package/dist/server/server-log.d.ts.map +1 -0
- package/dist/server/server-log.js +96 -0
- package/dist/server/server-log.js.map +1 -0
- package/dist/server/server.d.ts +389 -0
- package/dist/server/server.d.ts.map +1 -0
- package/dist/server/server.js +752 -0
- package/dist/server/server.js.map +1 -0
- package/dist/services/spine-services.d.ts +88 -0
- package/dist/services/spine-services.d.ts.map +1 -0
- package/dist/services/spine-services.js +1586 -0
- package/dist/services/spine-services.js.map +1 -0
- package/dist/stand/stand.d.ts +294 -0
- package/dist/stand/stand.d.ts.map +1 -0
- package/dist/stand/stand.js +680 -0
- package/dist/stand/stand.js.map +1 -0
- package/dist/stand/subscription-observer.d.ts +53 -0
- package/dist/stand/subscription-observer.d.ts.map +1 -0
- package/dist/stand/subscription-observer.js +444 -0
- package/dist/stand/subscription-observer.js.map +1 -0
- package/dist/stand/subscription-records.d.ts +12 -0
- package/dist/stand/subscription-records.d.ts.map +1 -0
- package/dist/stand/subscription-records.js +99 -0
- package/dist/stand/subscription-records.js.map +1 -0
- package/dist/stand/subscription-registry.d.ts +285 -0
- package/dist/stand/subscription-registry.d.ts.map +1 -0
- package/dist/stand/subscription-registry.js +403 -0
- package/dist/stand/subscription-registry.js.map +1 -0
- package/dist/stand/subscription-runtime.d.ts +102 -0
- package/dist/stand/subscription-runtime.d.ts.map +1 -0
- package/dist/stand/subscription-runtime.js +380 -0
- package/dist/stand/subscription-runtime.js.map +1 -0
- package/dist/testing/index.d.ts +14 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +34 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +65 -0
|
@@ -0,0 +1,937 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026, CodeMatters. All rights reserved.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
|
5
|
+
* in compliance with the License. You may obtain a copy of the License at
|
|
6
|
+
*
|
|
7
|
+
* https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
*
|
|
9
|
+
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
|
10
|
+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
|
11
|
+
* or implied. See the License for the specific language governing permissions and limitations under
|
|
12
|
+
* the License.
|
|
13
|
+
*/
|
|
14
|
+
import { fork } from "node:child_process";
|
|
15
|
+
import { randomUUID } from "node:crypto";
|
|
16
|
+
import { fileURLToPath } from "node:url";
|
|
17
|
+
import { LogLayer, StructuredTransport } from "loglayer";
|
|
18
|
+
import { runningServerAccess } from "./server.js";
|
|
19
|
+
import { emitServerWarning } from "./server-log.js";
|
|
20
|
+
import { NodeCoordinator } from "./node-coordinator.js";
|
|
21
|
+
import { environmentDeliveryWorkerAccess } from "./environment-delivery-worker.js";
|
|
22
|
+
import { managedChildSubscriptionAccess } from "./managed-child-subscription.js";
|
|
23
|
+
import { InMemorySubscriptionRegistry } from "../stand/subscription-registry.js";
|
|
24
|
+
const childMarker = "SPINE_MANAGED_SERVER_CHILD";
|
|
25
|
+
const slotMarker = "SPINE_MANAGED_SERVER_SLOT";
|
|
26
|
+
const incarnationMarker = "SPINE_MANAGED_SERVER_INCARNATION";
|
|
27
|
+
const initialRestartDelayMs = 250;
|
|
28
|
+
const maximumRestartDelayMs = 30_000;
|
|
29
|
+
const healthyReadyMs = 60_000;
|
|
30
|
+
const closeGraceMs = 1_000;
|
|
31
|
+
const closeKillMs = 1_000;
|
|
32
|
+
const endpointMaximumBytes = 256;
|
|
33
|
+
const handleMembers = new WeakMap();
|
|
34
|
+
const managedTestRegistries = new WeakMap();
|
|
35
|
+
const lifecycleLogger = new LogLayer({
|
|
36
|
+
transport: new StructuredTransport({
|
|
37
|
+
logger: console,
|
|
38
|
+
level: "warn",
|
|
39
|
+
stringify: true,
|
|
40
|
+
messageField: "message",
|
|
41
|
+
dateField: "timestamp",
|
|
42
|
+
levelField: "severity",
|
|
43
|
+
levelFn: (level) => level.toUpperCase(),
|
|
44
|
+
}),
|
|
45
|
+
}).child();
|
|
46
|
+
/**
|
|
47
|
+
*
|
|
48
|
+
* Starts a managed parent and its complete-replica child processes.
|
|
49
|
+
*/
|
|
50
|
+
export const ManagedServerApplication = Object.freeze({
|
|
51
|
+
async start(options) {
|
|
52
|
+
return ManagedServerValues.start(options, false);
|
|
53
|
+
},
|
|
54
|
+
async run(options) {
|
|
55
|
+
return ManagedServerValues.start(options, true);
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
const ManagedServerValues = Object.freeze({
|
|
59
|
+
async start(options, manageProcessSignals) {
|
|
60
|
+
if (!Number.isSafeInteger(options.processCount) || options.processCount < 1) {
|
|
61
|
+
throw new Error("Managed server processCount must be a positive safe integer.");
|
|
62
|
+
}
|
|
63
|
+
ManagedServerCoordinatorValues.coordinatorPort(options.port);
|
|
64
|
+
if (process.env[childMarker] === "true")
|
|
65
|
+
return ManagedServerValues.child(options);
|
|
66
|
+
return ManagedServerValues.parent(options, manageProcessSignals);
|
|
67
|
+
},
|
|
68
|
+
async child(options) {
|
|
69
|
+
const server = await options.createServer({ host: "127.0.0.1", port: 0 });
|
|
70
|
+
try {
|
|
71
|
+
ManagedServerValues.requireVolatileRegistries(server);
|
|
72
|
+
await options.synchronize?.();
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
try {
|
|
76
|
+
await server.close();
|
|
77
|
+
}
|
|
78
|
+
catch (cleanup) {
|
|
79
|
+
throw new AggregateError([error, cleanup], "Managed child setup and server cleanup failed.");
|
|
80
|
+
}
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
// Node may emit an IPC EPIPE after a parent has already disappeared. The
|
|
84
|
+
// disconnect handler below owns shutdown; this listener prevents that
|
|
85
|
+
// transport detail from becoming an unhandled child-process exception.
|
|
86
|
+
const containIpcError = () => undefined;
|
|
87
|
+
process.on("error", containIpcError);
|
|
88
|
+
managedChildSubscriptionAccess.install((subscriptionId) => {
|
|
89
|
+
const notification = ManagedServerValues.send({
|
|
90
|
+
type: "subscription-installed",
|
|
91
|
+
slot: process.env[slotMarker],
|
|
92
|
+
incarnation: process.env[incarnationMarker],
|
|
93
|
+
subscriptionId,
|
|
94
|
+
});
|
|
95
|
+
// spine-log-boundary: server.managed_replica_subscription_ipc
|
|
96
|
+
void notification.catch(() => undefined);
|
|
97
|
+
});
|
|
98
|
+
let activated = false;
|
|
99
|
+
let releaseActivation;
|
|
100
|
+
const activation = new Promise((resolve) => {
|
|
101
|
+
releaseActivation = resolve;
|
|
102
|
+
});
|
|
103
|
+
let closing;
|
|
104
|
+
const close = () => {
|
|
105
|
+
if (closing !== undefined)
|
|
106
|
+
return closing;
|
|
107
|
+
const draining = ManagedServerValues.send({
|
|
108
|
+
type: "draining",
|
|
109
|
+
slot: process.env[slotMarker],
|
|
110
|
+
incarnation: process.env[incarnationMarker],
|
|
111
|
+
});
|
|
112
|
+
// spine-log-boundary: server.managed_replica_drain_ipc
|
|
113
|
+
const attempt = draining
|
|
114
|
+
.catch(() => undefined)
|
|
115
|
+
.then(() => {
|
|
116
|
+
environmentDeliveryWorkerAccess.cancelManagedChild();
|
|
117
|
+
managedChildSubscriptionAccess.clear();
|
|
118
|
+
releaseActivation();
|
|
119
|
+
return runningServerAccess.drainDelivery(server);
|
|
120
|
+
})
|
|
121
|
+
.then(() => {
|
|
122
|
+
return server.close();
|
|
123
|
+
})
|
|
124
|
+
.then(() => {
|
|
125
|
+
const closed = ManagedServerValues.send({
|
|
126
|
+
type: "closed",
|
|
127
|
+
slot: process.env[slotMarker],
|
|
128
|
+
incarnation: process.env[incarnationMarker],
|
|
129
|
+
});
|
|
130
|
+
// spine-log-boundary: server.managed_replica_closed_ipc
|
|
131
|
+
return closed.catch(() => undefined);
|
|
132
|
+
}, (error) => {
|
|
133
|
+
const failed = ManagedServerValues.send({
|
|
134
|
+
type: "close-failed",
|
|
135
|
+
slot: process.env[slotMarker],
|
|
136
|
+
incarnation: process.env[incarnationMarker],
|
|
137
|
+
});
|
|
138
|
+
// spine-log-boundary: server.managed_replica_close_failed_ipc
|
|
139
|
+
void failed.catch(() => undefined);
|
|
140
|
+
if (closing === attempt)
|
|
141
|
+
closing = undefined;
|
|
142
|
+
throw error;
|
|
143
|
+
})
|
|
144
|
+
.then(() => {
|
|
145
|
+
process.off("disconnect", onDisconnect);
|
|
146
|
+
if (process.connected && typeof process.disconnect === "function")
|
|
147
|
+
process.disconnect();
|
|
148
|
+
process.off("error", containIpcError);
|
|
149
|
+
process.off("message", onMessage);
|
|
150
|
+
});
|
|
151
|
+
closing = attempt;
|
|
152
|
+
return attempt;
|
|
153
|
+
};
|
|
154
|
+
const onMessage = (message) => {
|
|
155
|
+
if (message.type === "activate" && !activated) {
|
|
156
|
+
activated = true;
|
|
157
|
+
environmentDeliveryWorkerAccess.activateManagedChild();
|
|
158
|
+
releaseActivation();
|
|
159
|
+
}
|
|
160
|
+
else if (message.type === "close") {
|
|
161
|
+
ManagedServerValues.closeFromEvent(close);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
const onDisconnect = () => {
|
|
165
|
+
ManagedServerValues.closeFromEvent(close);
|
|
166
|
+
};
|
|
167
|
+
process.on("message", onMessage);
|
|
168
|
+
process.once("disconnect", onDisconnect);
|
|
169
|
+
try {
|
|
170
|
+
await ManagedServerValues.send({
|
|
171
|
+
type: "synchronizing",
|
|
172
|
+
slot: process.env[slotMarker],
|
|
173
|
+
incarnation: process.env[incarnationMarker],
|
|
174
|
+
endpoint: server.baseUrl,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
await close();
|
|
179
|
+
throw error;
|
|
180
|
+
}
|
|
181
|
+
await activation;
|
|
182
|
+
await ManagedServerValues.send({
|
|
183
|
+
type: "ready",
|
|
184
|
+
slot: process.env[slotMarker],
|
|
185
|
+
incarnation: process.env[incarnationMarker],
|
|
186
|
+
endpoint: server.baseUrl,
|
|
187
|
+
});
|
|
188
|
+
return {
|
|
189
|
+
ready: true,
|
|
190
|
+
close,
|
|
191
|
+
};
|
|
192
|
+
},
|
|
193
|
+
parent(options, manageProcessSignals) {
|
|
194
|
+
return new ManagedServerCoordinator({ ...options, manageProcessSignals }).start();
|
|
195
|
+
},
|
|
196
|
+
closeFromEvent(close) {
|
|
197
|
+
// spine-log-boundary: server.managed_replica_child_close
|
|
198
|
+
void close().catch(() => {
|
|
199
|
+
emitServerWarning(lifecycleLogger, "Managed child close failed.", {
|
|
200
|
+
operation: "managed_replica.child_close",
|
|
201
|
+
reasonCode: "close_failed",
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
},
|
|
205
|
+
requireVolatileRegistries(server) {
|
|
206
|
+
const registries = runningServerAccess.subscriptionRegistries(server) ?? managedTestRegistries.get(server);
|
|
207
|
+
if (registries === undefined ||
|
|
208
|
+
registries.some((registry) => !(registry instanceof InMemorySubscriptionRegistry)))
|
|
209
|
+
throw new Error("Managed application replicas require an in-memory Stand subscription registry.");
|
|
210
|
+
},
|
|
211
|
+
send(message) {
|
|
212
|
+
return new Promise((resolve, reject) => {
|
|
213
|
+
if (process.send === undefined || !process.connected) {
|
|
214
|
+
reject(new Error("Managed child has no parent IPC channel."));
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
try {
|
|
218
|
+
process.send(message, (error) => {
|
|
219
|
+
if (error === null)
|
|
220
|
+
resolve();
|
|
221
|
+
else
|
|
222
|
+
reject(error);
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
reject(error instanceof Error ? error : new Error("Managed child READY IPC failed."));
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
/**
|
|
232
|
+
*
|
|
233
|
+
* Coordinates the parent-only lifecycle of managed application replicas.
|
|
234
|
+
*
|
|
235
|
+
* @internal
|
|
236
|
+
*/
|
|
237
|
+
export class ManagedServerCoordinator {
|
|
238
|
+
#slots;
|
|
239
|
+
#restart;
|
|
240
|
+
#options;
|
|
241
|
+
#dependencies;
|
|
242
|
+
#closing = false;
|
|
243
|
+
#starts = 0;
|
|
244
|
+
#ready;
|
|
245
|
+
#resolveReady;
|
|
246
|
+
#close;
|
|
247
|
+
#retired = new Set();
|
|
248
|
+
#retireTerminations = new Map();
|
|
249
|
+
#memberListeners = new Set();
|
|
250
|
+
#onSignal;
|
|
251
|
+
#nodeCoordinator;
|
|
252
|
+
/**
|
|
253
|
+
*
|
|
254
|
+
* Creates a coordinator for one configured replica cohort.
|
|
255
|
+
*
|
|
256
|
+
* @param options Configures complete child application replicas.
|
|
257
|
+
* @param dependencies Supplies private process and clock dependencies.
|
|
258
|
+
*/
|
|
259
|
+
constructor(options, dependencies = ManagedServerCoordinatorValues.dependencies) {
|
|
260
|
+
this.#options = options;
|
|
261
|
+
this.#dependencies = dependencies;
|
|
262
|
+
this.#restart = ManagedServerCoordinatorValues.restart(options);
|
|
263
|
+
this.#slots = Array.from({ length: options.processCount }, (_, slot) => ({
|
|
264
|
+
slot,
|
|
265
|
+
failures: 0,
|
|
266
|
+
initialReady: false,
|
|
267
|
+
starting: false,
|
|
268
|
+
replacementTimer: undefined,
|
|
269
|
+
replica: undefined,
|
|
270
|
+
}));
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
*
|
|
274
|
+
* Starts every initial replica and waits until all report READY.
|
|
275
|
+
*
|
|
276
|
+
* @returns The handle for the started managed cohort.
|
|
277
|
+
*/
|
|
278
|
+
async start() {
|
|
279
|
+
this.#ready = new Promise((resolve) => {
|
|
280
|
+
this.#resolveReady = resolve;
|
|
281
|
+
});
|
|
282
|
+
if (this.#options.manageProcessSignals !== false)
|
|
283
|
+
this.#installSignalHandlers();
|
|
284
|
+
for (const slot of this.#slots)
|
|
285
|
+
this.#start(slot);
|
|
286
|
+
await this.#ready;
|
|
287
|
+
try {
|
|
288
|
+
const openCoordinator = this.#dependencies.openCoordinator;
|
|
289
|
+
if (openCoordinator !== undefined)
|
|
290
|
+
this.#nodeCoordinator = await openCoordinator({
|
|
291
|
+
members: this,
|
|
292
|
+
...(this.#options.host === undefined ? {} : { host: this.#options.host }),
|
|
293
|
+
...(this.#options.port === undefined ? {} : { port: this.#options.port }),
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
catch (error) {
|
|
297
|
+
try {
|
|
298
|
+
await this.close();
|
|
299
|
+
}
|
|
300
|
+
catch (rollback) {
|
|
301
|
+
throw new AggregateError([error, rollback], "Managed replica Coordinator start and rollback failed.");
|
|
302
|
+
}
|
|
303
|
+
throw error;
|
|
304
|
+
}
|
|
305
|
+
const isReady = () => this.#slots.some((slot) => slot.replica?.readyAt !== undefined);
|
|
306
|
+
const handle = {
|
|
307
|
+
get ready() {
|
|
308
|
+
return isReady();
|
|
309
|
+
},
|
|
310
|
+
close: () => this.close(),
|
|
311
|
+
};
|
|
312
|
+
handleMembers.set(handle, this);
|
|
313
|
+
return handle;
|
|
314
|
+
}
|
|
315
|
+
#start(slot) {
|
|
316
|
+
if (this.#closing ||
|
|
317
|
+
slot.starting ||
|
|
318
|
+
slot.replica !== undefined ||
|
|
319
|
+
this.#starts >= this.#restart.concurrentStarts)
|
|
320
|
+
return;
|
|
321
|
+
slot.starting = true;
|
|
322
|
+
this.#starts++;
|
|
323
|
+
const incarnation = randomUUID();
|
|
324
|
+
let child;
|
|
325
|
+
try {
|
|
326
|
+
child = this.#dependencies.spawn(this.#options.moduleUrl, slot.slot, incarnation);
|
|
327
|
+
}
|
|
328
|
+
catch {
|
|
329
|
+
slot.starting = false;
|
|
330
|
+
this.#starts--;
|
|
331
|
+
// spine-log-boundary: server.managed_replica_start
|
|
332
|
+
emitServerWarning(lifecycleLogger, "Managed replica start failed.", {
|
|
333
|
+
operation: "managed_replica.start",
|
|
334
|
+
reasonCode: "start_failed",
|
|
335
|
+
slot: String(slot.slot),
|
|
336
|
+
incarnation,
|
|
337
|
+
attempt: String(slot.failures + 1),
|
|
338
|
+
});
|
|
339
|
+
this.#scheduleReplacement(slot);
|
|
340
|
+
this.#drainStarts();
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
const replica = {
|
|
344
|
+
slot: slot.slot,
|
|
345
|
+
incarnation,
|
|
346
|
+
child,
|
|
347
|
+
endpoint: undefined,
|
|
348
|
+
readyAt: undefined,
|
|
349
|
+
synchronizing: false,
|
|
350
|
+
draining: false,
|
|
351
|
+
subscriptionWaiters: new Map(),
|
|
352
|
+
closeAttempt: undefined,
|
|
353
|
+
expectedExit: false,
|
|
354
|
+
terminal: false,
|
|
355
|
+
};
|
|
356
|
+
slot.replica = replica;
|
|
357
|
+
child.on("message", (message) => {
|
|
358
|
+
this.#onMessage(slot, replica, message);
|
|
359
|
+
});
|
|
360
|
+
child.once("exit", () => {
|
|
361
|
+
this.#retired.delete(replica);
|
|
362
|
+
this.#retireTerminations.delete(replica);
|
|
363
|
+
this.#onExit(slot, replica);
|
|
364
|
+
});
|
|
365
|
+
child.once("error", () => {
|
|
366
|
+
this.#onError(slot, replica);
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
#onMessage(slot, replica, message) {
|
|
370
|
+
const closeResult = ManagedServerCoordinatorValues.closeResultMessage(message, slot.slot, replica.incarnation);
|
|
371
|
+
if (closeResult !== undefined) {
|
|
372
|
+
if (slot.replica !== replica || !replica.draining || replica.closeAttempt === undefined)
|
|
373
|
+
return;
|
|
374
|
+
if (closeResult === "closed")
|
|
375
|
+
replica.closeAttempt.outcome.resolve(undefined);
|
|
376
|
+
else
|
|
377
|
+
replica.closeAttempt.outcome.reject(new Error("Managed child server close failed."));
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
if (ManagedServerCoordinatorValues.subscriptionInstalledMessage(message, slot.slot, replica.incarnation)) {
|
|
381
|
+
if (slot.replica !== replica)
|
|
382
|
+
return;
|
|
383
|
+
this.#settleSubscriptionWaiter(replica, message.subscriptionId);
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
if (ManagedServerCoordinatorValues.synchronizingMessage(message, slot.slot, replica.incarnation)) {
|
|
387
|
+
if (slot.replica !== replica || replica.endpoint !== undefined)
|
|
388
|
+
return;
|
|
389
|
+
replica.endpoint = message.endpoint;
|
|
390
|
+
replica.synchronizing = true;
|
|
391
|
+
this.#notifyReadyMembers();
|
|
392
|
+
if (this.#nodeCoordinator === undefined)
|
|
393
|
+
replica.child.send({ type: "activate" }, () => undefined);
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
if (ManagedServerCoordinatorValues.drainingMessage(message, slot.slot, replica.incarnation)) {
|
|
397
|
+
if (slot.replica !== replica)
|
|
398
|
+
return;
|
|
399
|
+
replica.closeAttempt?.acknowledged.resolve(undefined);
|
|
400
|
+
if (replica.draining)
|
|
401
|
+
return;
|
|
402
|
+
replica.draining = true;
|
|
403
|
+
replica.expectedExit = true;
|
|
404
|
+
this.#notifyReadyMembers();
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
if (!ManagedServerCoordinatorValues.readyMessage(message, slot.slot, replica.incarnation))
|
|
408
|
+
return;
|
|
409
|
+
if (slot.replica !== replica || replica.readyAt !== undefined || this.#closing)
|
|
410
|
+
return;
|
|
411
|
+
slot.starting = false;
|
|
412
|
+
this.#starts--;
|
|
413
|
+
replica.synchronizing = false;
|
|
414
|
+
replica.endpoint = message.endpoint;
|
|
415
|
+
replica.readyAt = this.#dependencies.clock.now();
|
|
416
|
+
slot.initialReady = true;
|
|
417
|
+
if (this.#slots.every((candidate) => candidate.initialReady))
|
|
418
|
+
this.#resolveReady?.();
|
|
419
|
+
this.#notifyReadyMembers();
|
|
420
|
+
this.#drainStarts();
|
|
421
|
+
}
|
|
422
|
+
#onExit(slot, replica) {
|
|
423
|
+
if (slot.replica !== replica || replica.terminal)
|
|
424
|
+
return;
|
|
425
|
+
replica.terminal = true;
|
|
426
|
+
this.#clearSubscriptionWaiters(replica, new Error("Managed child exited before subscription installed."));
|
|
427
|
+
slot.replica = undefined;
|
|
428
|
+
this.#notifyReadyMembers();
|
|
429
|
+
if (slot.starting) {
|
|
430
|
+
slot.starting = false;
|
|
431
|
+
this.#starts--;
|
|
432
|
+
}
|
|
433
|
+
if (this.#closing || replica.expectedExit)
|
|
434
|
+
return;
|
|
435
|
+
if (replica.readyAt !== undefined &&
|
|
436
|
+
this.#dependencies.clock.now() - replica.readyAt >= this.#restart.healthyReadyMs) {
|
|
437
|
+
slot.failures = 0;
|
|
438
|
+
}
|
|
439
|
+
// spine-log-boundary: server.managed_replica_exit
|
|
440
|
+
emitServerWarning(lifecycleLogger, "Managed replica exited unexpectedly.", {
|
|
441
|
+
operation: "managed_replica.exit",
|
|
442
|
+
reasonCode: "unexpected_exit",
|
|
443
|
+
slot: String(slot.slot),
|
|
444
|
+
incarnation: replica.incarnation,
|
|
445
|
+
attempt: String(slot.failures + 1),
|
|
446
|
+
});
|
|
447
|
+
this.#scheduleReplacement(slot);
|
|
448
|
+
this.#drainStarts();
|
|
449
|
+
}
|
|
450
|
+
#onError(slot, replica) {
|
|
451
|
+
if (slot.replica !== replica || replica.terminal)
|
|
452
|
+
return;
|
|
453
|
+
// A ChildProcess error can precede exit while the OS process still lives.
|
|
454
|
+
// Retire the incarnation once, then actively terminate that known child.
|
|
455
|
+
this.#onExit(slot, replica);
|
|
456
|
+
this.#terminateUnexpected(replica);
|
|
457
|
+
}
|
|
458
|
+
#scheduleReplacement(slot) {
|
|
459
|
+
slot.failures++;
|
|
460
|
+
const delay = Math.min(this.#restart.initialDelayMs * 2 ** Math.max(0, slot.failures - 1), this.#restart.maximumDelayMs);
|
|
461
|
+
slot.replacementTimer = this.#dependencies.clock.setTimeout(() => {
|
|
462
|
+
slot.replacementTimer = undefined;
|
|
463
|
+
this.#start(slot);
|
|
464
|
+
}, delay);
|
|
465
|
+
// spine-log-boundary: server.managed_replica_retry
|
|
466
|
+
emitServerWarning(lifecycleLogger, "Managed replica replacement scheduled.", {
|
|
467
|
+
operation: "managed_replica.replace",
|
|
468
|
+
reasonCode: "unexpected_exit",
|
|
469
|
+
slot: String(slot.slot),
|
|
470
|
+
attempt: String(slot.failures),
|
|
471
|
+
delay: String(delay),
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
#drainStarts() {
|
|
475
|
+
for (const slot of this.#slots) {
|
|
476
|
+
if (this.#starts >= this.#restart.concurrentStarts)
|
|
477
|
+
return;
|
|
478
|
+
if (slot.replica === undefined && slot.replacementTimer === undefined)
|
|
479
|
+
this.#start(slot);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
*
|
|
484
|
+
* Stops managed children and prevents later replacement starts.
|
|
485
|
+
*
|
|
486
|
+
* @returns Completion after child and retired-child cleanup succeeds.
|
|
487
|
+
*/
|
|
488
|
+
close() {
|
|
489
|
+
const closing = this.#close;
|
|
490
|
+
if (closing !== undefined)
|
|
491
|
+
return closing;
|
|
492
|
+
const close = (async () => {
|
|
493
|
+
this.#closing = true;
|
|
494
|
+
this.#removeSignalHandlers();
|
|
495
|
+
for (const slot of this.#slots) {
|
|
496
|
+
if (slot.replacementTimer !== undefined)
|
|
497
|
+
this.#dependencies.clock.clearTimeout(slot.replacementTimer);
|
|
498
|
+
slot.replacementTimer = undefined;
|
|
499
|
+
}
|
|
500
|
+
// `beginDrain()` makes the unary snapshot empty synchronously. Start every
|
|
501
|
+
// child quiescence attempt without waiting for its reconciliation promise:
|
|
502
|
+
// child close IPC must be issued in this turn, while the Coordinator keeps
|
|
503
|
+
// subscription relays alive until every child has settled.
|
|
504
|
+
const draining = this.#nodeCoordinator?.beginDrain();
|
|
505
|
+
for (const slot of this.#slots)
|
|
506
|
+
if (slot.replica !== undefined)
|
|
507
|
+
this.#clearSubscriptionWaiters(slot.replica, new Error("Managed application is closing before subscription installed."));
|
|
508
|
+
const childCloses = [
|
|
509
|
+
...this.#slots.map((slot) => this.#closeReplica(slot.replica)),
|
|
510
|
+
...[...this.#retired].map((replica) => this.#closeRetired(replica)),
|
|
511
|
+
];
|
|
512
|
+
const results = await Promise.allSettled([draining, ...childCloses]);
|
|
513
|
+
const failures = [];
|
|
514
|
+
for (const result of results)
|
|
515
|
+
if (result.status === "rejected")
|
|
516
|
+
failures.push(result.reason);
|
|
517
|
+
if (failures.length === 1)
|
|
518
|
+
throw failures[0];
|
|
519
|
+
if (failures.length > 1)
|
|
520
|
+
throw new AggregateError(failures, "Managed child quiescence failed during drain.");
|
|
521
|
+
await this.#nodeCoordinator?.close();
|
|
522
|
+
})();
|
|
523
|
+
this.#close = close;
|
|
524
|
+
void close.then(undefined, () => {
|
|
525
|
+
if (this.#close === close)
|
|
526
|
+
this.#close = undefined;
|
|
527
|
+
});
|
|
528
|
+
return close;
|
|
529
|
+
}
|
|
530
|
+
#closeReplica(replica) {
|
|
531
|
+
return ManagedServerCoordinatorValues.close(replica, this.#dependencies.clock);
|
|
532
|
+
}
|
|
533
|
+
async #closeRetired(replica) {
|
|
534
|
+
replica.expectedExit = true;
|
|
535
|
+
await this.#terminateRetired(replica);
|
|
536
|
+
this.#retired.delete(replica);
|
|
537
|
+
}
|
|
538
|
+
#terminateUnexpected(replica) {
|
|
539
|
+
this.#retired.add(replica);
|
|
540
|
+
void this.#terminateRetired(replica);
|
|
541
|
+
}
|
|
542
|
+
#terminateRetired(replica) {
|
|
543
|
+
const prior = this.#retireTerminations.get(replica);
|
|
544
|
+
if (prior !== undefined)
|
|
545
|
+
return prior;
|
|
546
|
+
const termination = ManagedServerCoordinatorValues.terminate(replica.child, this.#dependencies.clock);
|
|
547
|
+
this.#retireTerminations.set(replica, termination);
|
|
548
|
+
void termination.then(() => {
|
|
549
|
+
this.#retireTerminations.delete(replica);
|
|
550
|
+
this.#retired.delete(replica);
|
|
551
|
+
}, () => {
|
|
552
|
+
this.#retireTerminations.delete(replica);
|
|
553
|
+
// spine-log-boundary: server.managed_replica_termination
|
|
554
|
+
emitServerWarning(lifecycleLogger, "Managed replica termination was bounded.", {
|
|
555
|
+
operation: "managed_replica.terminate",
|
|
556
|
+
reasonCode: "unresponsive",
|
|
557
|
+
slot: String(replica.slot),
|
|
558
|
+
incarnation: replica.incarnation,
|
|
559
|
+
});
|
|
560
|
+
});
|
|
561
|
+
return termination;
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
*
|
|
565
|
+
* Returns ready child facts to the next Coordinator slice.
|
|
566
|
+
*
|
|
567
|
+
* @returns The ready child topology facts.
|
|
568
|
+
* @internal
|
|
569
|
+
*/
|
|
570
|
+
readyMembers() {
|
|
571
|
+
return this.#members(false);
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Returns READY and synchronizing members whose relays remain eligible.
|
|
575
|
+
*
|
|
576
|
+
* @returns Private relay member facts.
|
|
577
|
+
*/
|
|
578
|
+
relayMembers() {
|
|
579
|
+
return this.#members(true);
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Records an exact native subscription whose activation started for a member.
|
|
583
|
+
*
|
|
584
|
+
* @param member Supplies the member that owns the child subscription.
|
|
585
|
+
* @param subscription Supplies the activating native subscription.
|
|
586
|
+
*/
|
|
587
|
+
onChildSubscriptionActivated(member, subscription) {
|
|
588
|
+
const replica = this.#replica(member);
|
|
589
|
+
const id = subscription.id?.value;
|
|
590
|
+
if (replica === undefined || id === undefined || replica.subscriptionWaiters.has(id))
|
|
591
|
+
return;
|
|
592
|
+
replica.subscriptionWaiters.set(id, ManagedServerCoordinatorValues.deferred());
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Clears a pending child-installation wait after cancellation.
|
|
596
|
+
*
|
|
597
|
+
* @param member Supplies the member that owns the cancelled subscription.
|
|
598
|
+
* @param subscription Supplies the cancelled native subscription.
|
|
599
|
+
*/
|
|
600
|
+
onChildSubscriptionCancelled(member, subscription) {
|
|
601
|
+
const replica = this.#replica(member);
|
|
602
|
+
const id = subscription.id?.value;
|
|
603
|
+
if (replica !== undefined && id !== undefined)
|
|
604
|
+
this.#settleSubscriptionWaiter(replica, id);
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Activates synchronizing children after their retained relay children install.
|
|
608
|
+
*
|
|
609
|
+
* @returns Completion after each current child receives private activation.
|
|
610
|
+
*/
|
|
611
|
+
async onRelaySynchronized() {
|
|
612
|
+
const synchronizing = this.#slots
|
|
613
|
+
.map((slot) => slot.replica)
|
|
614
|
+
.filter((replica) => replica !== undefined && replica.synchronizing && replica.readyAt === undefined);
|
|
615
|
+
await Promise.all(synchronizing.flatMap((replica) => [...replica.subscriptionWaiters.values()].map((waiter) => waiter.promise)));
|
|
616
|
+
for (const replica of synchronizing)
|
|
617
|
+
if (!replica.terminal && replica.synchronizing && replica.readyAt === undefined)
|
|
618
|
+
replica.child.send({ type: "activate" }, () => undefined);
|
|
619
|
+
}
|
|
620
|
+
#replica(member) {
|
|
621
|
+
const replica = this.#slots[member.slot]?.replica;
|
|
622
|
+
return replica?.incarnation === member.incarnation ? replica : undefined;
|
|
623
|
+
}
|
|
624
|
+
#clearSubscriptionWaiters(replica, error) {
|
|
625
|
+
for (const waiter of replica.subscriptionWaiters.values())
|
|
626
|
+
waiter.reject(error);
|
|
627
|
+
replica.subscriptionWaiters.clear();
|
|
628
|
+
}
|
|
629
|
+
#settleSubscriptionWaiter(replica, id) {
|
|
630
|
+
const waiter = replica.subscriptionWaiters.get(id);
|
|
631
|
+
if (waiter === undefined)
|
|
632
|
+
return;
|
|
633
|
+
replica.subscriptionWaiters.delete(id);
|
|
634
|
+
waiter.resolve(undefined);
|
|
635
|
+
}
|
|
636
|
+
#members(includeDraining) {
|
|
637
|
+
return this.#slots.flatMap((slot) => {
|
|
638
|
+
const replica = slot.replica;
|
|
639
|
+
if ((replica?.readyAt === undefined && !(includeDraining && replica?.synchronizing)) ||
|
|
640
|
+
(!includeDraining && replica.draining) ||
|
|
641
|
+
replica.endpoint === undefined ||
|
|
642
|
+
replica.child.pid === undefined)
|
|
643
|
+
return [];
|
|
644
|
+
return [
|
|
645
|
+
{
|
|
646
|
+
slot: slot.slot,
|
|
647
|
+
incarnation: replica.incarnation,
|
|
648
|
+
pid: replica.child.pid,
|
|
649
|
+
endpoint: replica.endpoint,
|
|
650
|
+
},
|
|
651
|
+
];
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Subscribes one private Coordinator to READY membership changes.
|
|
656
|
+
*
|
|
657
|
+
* @param onChange Runs after an admitted READY member or removed member changes.
|
|
658
|
+
* @returns Stops later private notifications.
|
|
659
|
+
* @internal
|
|
660
|
+
*/
|
|
661
|
+
onReadyMembersChange(onChange) {
|
|
662
|
+
this.#memberListeners.add(onChange);
|
|
663
|
+
return () => this.#memberListeners.delete(onChange);
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Subscribes one private Coordinator to relay-membership changes.
|
|
667
|
+
*
|
|
668
|
+
* @param onChange Runs after a relay member is admitted or removed.
|
|
669
|
+
* @returns Stops later relay-membership callbacks.
|
|
670
|
+
*/
|
|
671
|
+
onRelayMembersChange(onChange) {
|
|
672
|
+
return this.onReadyMembersChange(onChange);
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Returns the private public-listener endpoint for coordinator acceptance.
|
|
676
|
+
*
|
|
677
|
+
* @returns The listener endpoint after managed startup, when open.
|
|
678
|
+
* @internal
|
|
679
|
+
*/
|
|
680
|
+
coordinatorEndpoint() {
|
|
681
|
+
return this.#nodeCoordinator?.baseUrl;
|
|
682
|
+
}
|
|
683
|
+
#notifyReadyMembers() {
|
|
684
|
+
for (const listener of this.#memberListeners)
|
|
685
|
+
listener();
|
|
686
|
+
}
|
|
687
|
+
#installSignalHandlers() {
|
|
688
|
+
if (this.#onSignal !== undefined)
|
|
689
|
+
return;
|
|
690
|
+
this.#onSignal = () => {
|
|
691
|
+
// spine-log-boundary: server.managed_replica_signal
|
|
692
|
+
void this.close()
|
|
693
|
+
.then(() => {
|
|
694
|
+
if (process.connected && typeof process.disconnect === "function")
|
|
695
|
+
process.disconnect();
|
|
696
|
+
})
|
|
697
|
+
.catch(() => {
|
|
698
|
+
emitServerWarning(lifecycleLogger, "Managed replica signal close failed.", {
|
|
699
|
+
operation: "managed_replica.signal",
|
|
700
|
+
reasonCode: "close_failed",
|
|
701
|
+
});
|
|
702
|
+
});
|
|
703
|
+
};
|
|
704
|
+
process.on("SIGINT", this.#onSignal);
|
|
705
|
+
process.on("SIGTERM", this.#onSignal);
|
|
706
|
+
}
|
|
707
|
+
#removeSignalHandlers() {
|
|
708
|
+
if (this.#onSignal === undefined)
|
|
709
|
+
return;
|
|
710
|
+
process.off("SIGINT", this.#onSignal);
|
|
711
|
+
process.off("SIGTERM", this.#onSignal);
|
|
712
|
+
this.#onSignal = undefined;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
*
|
|
717
|
+
* Exposes private Coordinator topology facts to the next internal slice.
|
|
718
|
+
*
|
|
719
|
+
* @internal
|
|
720
|
+
*/
|
|
721
|
+
export const managedServerCoordinatorAccess = Object.freeze({
|
|
722
|
+
readyMembers(coordinator) {
|
|
723
|
+
return coordinator.readyMembers();
|
|
724
|
+
},
|
|
725
|
+
coordinatorEndpoint(coordinator) {
|
|
726
|
+
return coordinator.coordinatorEndpoint();
|
|
727
|
+
},
|
|
728
|
+
});
|
|
729
|
+
/**
|
|
730
|
+
*
|
|
731
|
+
* Exposes private managed-child facts to Coordinator forwarding tests.
|
|
732
|
+
*
|
|
733
|
+
* @internal
|
|
734
|
+
*/
|
|
735
|
+
export const managedServerApplicationAccess = Object.freeze({
|
|
736
|
+
readyMembers(handle) {
|
|
737
|
+
return handleMembers.get(handle)?.readyMembers() ?? [];
|
|
738
|
+
},
|
|
739
|
+
coordinatorEndpoint(handle) {
|
|
740
|
+
return handleMembers.get(handle)?.coordinatorEndpoint();
|
|
741
|
+
},
|
|
742
|
+
installRegistriesForTest(server, registries) {
|
|
743
|
+
managedTestRegistries.set(server, registries);
|
|
744
|
+
},
|
|
745
|
+
});
|
|
746
|
+
const ManagedServerCoordinatorValues = Object.freeze({
|
|
747
|
+
deferred() {
|
|
748
|
+
let resolve;
|
|
749
|
+
let reject;
|
|
750
|
+
const promise = new Promise((next, fail) => {
|
|
751
|
+
resolve = next;
|
|
752
|
+
reject = fail;
|
|
753
|
+
});
|
|
754
|
+
return { promise, resolve, reject };
|
|
755
|
+
},
|
|
756
|
+
coordinatorPort(value) {
|
|
757
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > 65_535)
|
|
758
|
+
throw new Error("Managed server Coordinator port must be a safe integer between 1 and 65535.");
|
|
759
|
+
return value;
|
|
760
|
+
},
|
|
761
|
+
dependencies: {
|
|
762
|
+
clock: {
|
|
763
|
+
now: () => Date.now(),
|
|
764
|
+
setTimeout: (onTimeout, delay) => setTimeout(onTimeout, delay),
|
|
765
|
+
clearTimeout: (timer) => {
|
|
766
|
+
clearTimeout(timer);
|
|
767
|
+
},
|
|
768
|
+
},
|
|
769
|
+
spawn: (moduleUrl, slot, incarnation) => fork(fileURLToPath(moduleUrl), [], {
|
|
770
|
+
env: {
|
|
771
|
+
...process.env,
|
|
772
|
+
[childMarker]: "true",
|
|
773
|
+
[slotMarker]: String(slot),
|
|
774
|
+
[incarnationMarker]: incarnation,
|
|
775
|
+
},
|
|
776
|
+
stdio: ["inherit", "inherit", "inherit", "ipc"],
|
|
777
|
+
}),
|
|
778
|
+
openCoordinator: (options) => NodeCoordinator.open(options),
|
|
779
|
+
},
|
|
780
|
+
restart(options) {
|
|
781
|
+
const restart = {
|
|
782
|
+
initialDelayMs: options.restart?.initialDelayMs ?? initialRestartDelayMs,
|
|
783
|
+
maximumDelayMs: options.restart?.maximumDelayMs ?? maximumRestartDelayMs,
|
|
784
|
+
healthyReadyMs: options.restart?.healthyReadyMs ?? healthyReadyMs,
|
|
785
|
+
concurrentStarts: options.restart?.concurrentStarts ?? Math.min(4, options.processCount),
|
|
786
|
+
};
|
|
787
|
+
for (const [name, value] of Object.entries(restart)) {
|
|
788
|
+
if (!Number.isSafeInteger(value) || value < 1)
|
|
789
|
+
throw new Error(`Managed server restart ${name} must be a positive safe integer.`);
|
|
790
|
+
}
|
|
791
|
+
if (restart.maximumDelayMs < restart.initialDelayMs)
|
|
792
|
+
throw new Error("Managed server restart maximumDelayMs must not be below initialDelayMs.");
|
|
793
|
+
if (restart.concurrentStarts > options.processCount)
|
|
794
|
+
throw new Error("Managed server restart concurrentStarts must not exceed processCount.");
|
|
795
|
+
return restart;
|
|
796
|
+
},
|
|
797
|
+
readyMessage(message, slot, incarnation) {
|
|
798
|
+
if (typeof message !== "object" || message === null)
|
|
799
|
+
return false;
|
|
800
|
+
const candidate = message;
|
|
801
|
+
return (candidate.type === "ready" &&
|
|
802
|
+
candidate.slot === String(slot) &&
|
|
803
|
+
candidate.incarnation === incarnation &&
|
|
804
|
+
typeof candidate.endpoint === "string" &&
|
|
805
|
+
canonicalLoopbackEndpoint(candidate.endpoint) !== undefined);
|
|
806
|
+
},
|
|
807
|
+
synchronizingMessage(message, slot, incarnation) {
|
|
808
|
+
if (typeof message !== "object" || message === null)
|
|
809
|
+
return false;
|
|
810
|
+
const candidate = message;
|
|
811
|
+
return (candidate.type === "synchronizing" &&
|
|
812
|
+
candidate.slot === String(slot) &&
|
|
813
|
+
candidate.incarnation === incarnation &&
|
|
814
|
+
typeof candidate.endpoint === "string" &&
|
|
815
|
+
canonicalLoopbackEndpoint(candidate.endpoint) !== undefined);
|
|
816
|
+
},
|
|
817
|
+
subscriptionInstalledMessage(message, slot, incarnation) {
|
|
818
|
+
if (typeof message !== "object" || message === null)
|
|
819
|
+
return false;
|
|
820
|
+
const candidate = message;
|
|
821
|
+
return (candidate.type === "subscription-installed" &&
|
|
822
|
+
candidate.slot === String(slot) &&
|
|
823
|
+
candidate.incarnation === incarnation &&
|
|
824
|
+
typeof candidate.subscriptionId === "string" &&
|
|
825
|
+
candidate.subscriptionId.length > 0);
|
|
826
|
+
},
|
|
827
|
+
drainingMessage(message, slot, incarnation) {
|
|
828
|
+
if (typeof message !== "object" || message === null)
|
|
829
|
+
return false;
|
|
830
|
+
const candidate = message;
|
|
831
|
+
return (candidate.type === "draining" &&
|
|
832
|
+
candidate.slot === String(slot) &&
|
|
833
|
+
candidate.incarnation === incarnation);
|
|
834
|
+
},
|
|
835
|
+
closeResultMessage(message, slot, incarnation) {
|
|
836
|
+
if (typeof message !== "object" || message === null)
|
|
837
|
+
return undefined;
|
|
838
|
+
const candidate = message;
|
|
839
|
+
if ((candidate.type !== "closed" && candidate.type !== "close-failed") ||
|
|
840
|
+
candidate.slot !== String(slot) ||
|
|
841
|
+
candidate.incarnation !== incarnation)
|
|
842
|
+
return undefined;
|
|
843
|
+
return candidate.type;
|
|
844
|
+
},
|
|
845
|
+
async close(replica, clock) {
|
|
846
|
+
if (replica === undefined)
|
|
847
|
+
return;
|
|
848
|
+
replica.expectedExit = true;
|
|
849
|
+
const { child } = replica;
|
|
850
|
+
if (child.exitCode !== null || child.signalCode !== null)
|
|
851
|
+
return;
|
|
852
|
+
const exited = new Promise((resolve) => child.once("exit", () => {
|
|
853
|
+
resolve();
|
|
854
|
+
}));
|
|
855
|
+
const closeAttempt = {
|
|
856
|
+
acknowledged: ManagedServerCoordinatorValues.deferred(),
|
|
857
|
+
outcome: ManagedServerCoordinatorValues.deferred(),
|
|
858
|
+
};
|
|
859
|
+
replica.closeAttempt = closeAttempt;
|
|
860
|
+
try {
|
|
861
|
+
if (child.connected)
|
|
862
|
+
child.send({ type: "close" }, () => undefined);
|
|
863
|
+
const observation = Promise.race([
|
|
864
|
+
closeAttempt.acknowledged.promise.then(() => "acknowledged"),
|
|
865
|
+
exited.then(() => "exited"),
|
|
866
|
+
]);
|
|
867
|
+
if (await ManagedServerCoordinatorValues.within(observation, closeGraceMs, clock)) {
|
|
868
|
+
if ((await observation) === "exited")
|
|
869
|
+
return;
|
|
870
|
+
await Promise.race([
|
|
871
|
+
closeAttempt.outcome.promise,
|
|
872
|
+
exited.then(() => {
|
|
873
|
+
throw new Error("Managed child exited before graceful close completed.");
|
|
874
|
+
}),
|
|
875
|
+
]);
|
|
876
|
+
await ManagedServerCoordinatorValues.terminate(child, clock, exited);
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
await ManagedServerCoordinatorValues.terminate(child, clock, exited, false);
|
|
880
|
+
}
|
|
881
|
+
finally {
|
|
882
|
+
if (replica.closeAttempt === closeAttempt)
|
|
883
|
+
replica.closeAttempt = undefined;
|
|
884
|
+
}
|
|
885
|
+
},
|
|
886
|
+
async terminate(child, clock, knownExit, allowGrace = true) {
|
|
887
|
+
if (child.exitCode !== null || child.signalCode !== null)
|
|
888
|
+
return;
|
|
889
|
+
const exited = knownExit ??
|
|
890
|
+
new Promise((resolve) => child.once("exit", () => {
|
|
891
|
+
resolve();
|
|
892
|
+
}));
|
|
893
|
+
if (allowGrace && (await ManagedServerCoordinatorValues.within(exited, closeGraceMs, clock)))
|
|
894
|
+
return;
|
|
895
|
+
child.kill("SIGTERM");
|
|
896
|
+
if (await ManagedServerCoordinatorValues.within(exited, closeKillMs, clock))
|
|
897
|
+
return;
|
|
898
|
+
child.kill("SIGKILL");
|
|
899
|
+
if (await ManagedServerCoordinatorValues.within(exited, closeKillMs, clock))
|
|
900
|
+
return;
|
|
901
|
+
throw new Error("Managed child did not exit after SIGKILL.");
|
|
902
|
+
},
|
|
903
|
+
within(promise, delay, clock) {
|
|
904
|
+
return new Promise((resolve) => {
|
|
905
|
+
const timer = clock.setTimeout(() => {
|
|
906
|
+
resolve(false);
|
|
907
|
+
}, delay);
|
|
908
|
+
void promise.then(() => {
|
|
909
|
+
clock.clearTimeout(timer);
|
|
910
|
+
resolve(true);
|
|
911
|
+
});
|
|
912
|
+
});
|
|
913
|
+
},
|
|
914
|
+
});
|
|
915
|
+
function canonicalLoopbackEndpoint(value) {
|
|
916
|
+
if (Buffer.byteLength(value, "utf8") > endpointMaximumBytes)
|
|
917
|
+
return undefined;
|
|
918
|
+
try {
|
|
919
|
+
const endpoint = new URL(value);
|
|
920
|
+
if (endpoint.protocol !== "http:" ||
|
|
921
|
+
endpoint.hostname !== "127.0.0.1" ||
|
|
922
|
+
endpoint.port === "" ||
|
|
923
|
+
endpoint.username !== "" ||
|
|
924
|
+
endpoint.password !== "" ||
|
|
925
|
+
endpoint.pathname !== "/" ||
|
|
926
|
+
endpoint.search !== "" ||
|
|
927
|
+
endpoint.hash !== "" ||
|
|
928
|
+
endpoint.origin !== value)
|
|
929
|
+
return undefined;
|
|
930
|
+
const port = Number(endpoint.port);
|
|
931
|
+
return Number.isSafeInteger(port) && port >= 1 && port <= 65_535 ? value : undefined;
|
|
932
|
+
}
|
|
933
|
+
catch {
|
|
934
|
+
return undefined;
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
//# sourceMappingURL=managed-server-application.js.map
|