rollbridge 0.1.23 → 0.1.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/package.json +1 -1
- package/src/daemon.js +3 -2
- package/test/shutdown-completion.test.js +57 -3
package/package.json
CHANGED
package/src/daemon.js
CHANGED
|
@@ -882,12 +882,13 @@ export default class RollbridgeDaemon {
|
|
|
882
882
|
}
|
|
883
883
|
|
|
884
884
|
await captureShutdownError(cleanupErrors, "proxy close", async () => this.proxy.close())
|
|
885
|
-
const
|
|
886
|
-
...[...this.services.values()].map((processInstance) => processInstance.stop()),
|
|
885
|
+
const dependentStopResults = await Promise.allSettled([
|
|
887
886
|
...[...this.singletons.values()].map((processInstance) => processInstance.stop()),
|
|
888
887
|
...[...this.startingReleases].map((release) => release.stop()),
|
|
889
888
|
...[...this.releases.values()].map((release) => release.stop())
|
|
890
889
|
])
|
|
890
|
+
const serviceStopResults = await Promise.allSettled([...this.services.values()].map((processInstance) => processInstance.stop()))
|
|
891
|
+
const stopResults = [...dependentStopResults, ...serviceStopResults]
|
|
891
892
|
await captureShutdownError(cleanupErrors, "proxy server close", () => this.closeServer(this.proxyServer))
|
|
892
893
|
|
|
893
894
|
// Wait for any in-flight write first so it can't recreate or overwrite the final state (no
|
|
@@ -115,6 +115,60 @@ test("shutdown response waits for endpoint and owned-process cleanup before imme
|
|
|
115
115
|
}
|
|
116
116
|
})
|
|
117
117
|
|
|
118
|
+
test("shutdown keeps daemon services alive until release-owned dependents stop", async () => {
|
|
119
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), "rollbridge-shutdown-service-order-"))
|
|
120
|
+
const socketPath = path.join(root, "control.sock")
|
|
121
|
+
const processCommand = `${JSON.stringify(process.execPath)} -e ${JSON.stringify("setInterval(() => {}, 1000)")}`
|
|
122
|
+
const config = buildConfig(socketPath, {
|
|
123
|
+
companion: {command: processCommand, gracefulStopMs: "indefinite", id: "worker", policy: "companion"},
|
|
124
|
+
service: {command: processCommand, id: "coordinator", policy: "service"}
|
|
125
|
+
})
|
|
126
|
+
const daemon = new RollbridgeDaemon({config, logger: () => {}})
|
|
127
|
+
/** @type {() => void} */
|
|
128
|
+
let releaseWorker = () => {}
|
|
129
|
+
const workerGate = new Promise((resolve) => { releaseWorker = () => resolve(undefined) })
|
|
130
|
+
let serviceStopStarted = false
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
await daemon.start()
|
|
134
|
+
await daemon.deploy({releaseId: "v1", releasePath: root, revision: "v1"})
|
|
135
|
+
|
|
136
|
+
const release = daemon.activeRelease
|
|
137
|
+
const coordinator = daemon.services.get("coordinator")
|
|
138
|
+
|
|
139
|
+
assert.ok(release)
|
|
140
|
+
assert.ok(coordinator)
|
|
141
|
+
|
|
142
|
+
const originalReleaseStop = release.stop.bind(release)
|
|
143
|
+
const originalCoordinatorStop = coordinator.stop.bind(coordinator)
|
|
144
|
+
let signalReleaseStopStarted = () => {}
|
|
145
|
+
const releaseStopStarted = new Promise((resolve) => { signalReleaseStopStarted = () => resolve(undefined) })
|
|
146
|
+
|
|
147
|
+
release.stop = async () => {
|
|
148
|
+
signalReleaseStopStarted()
|
|
149
|
+
await workerGate
|
|
150
|
+
await originalReleaseStop()
|
|
151
|
+
}
|
|
152
|
+
coordinator.stop = async () => {
|
|
153
|
+
serviceStopStarted = true
|
|
154
|
+
await originalCoordinatorStop()
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const shutdown = daemon.shutdown()
|
|
158
|
+
|
|
159
|
+
await releaseStopStarted
|
|
160
|
+
const serviceStoppedWhileWorkerWasDraining = serviceStopStarted
|
|
161
|
+
releaseWorker()
|
|
162
|
+
await shutdown
|
|
163
|
+
|
|
164
|
+
assert.equal(serviceStoppedWhileWorkerWasDraining, false, "a worker must retain access to daemon services throughout its drain")
|
|
165
|
+
} finally {
|
|
166
|
+
releaseWorker()
|
|
167
|
+
await daemon.shutdown()
|
|
168
|
+
await fs.rm(root, {force: true, recursive: true})
|
|
169
|
+
}
|
|
170
|
+
})
|
|
171
|
+
|
|
118
172
|
test("external-owner retirement releases listeners before a long-draining companion exits", async () => {
|
|
119
173
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "rollbridge-owner-retirement-"))
|
|
120
174
|
const socketPath = path.join(root, "rollbridge.sock")
|
|
@@ -314,13 +368,13 @@ test("shutdown of an already-stopped endpoint fails explicitly", async () => {
|
|
|
314
368
|
|
|
315
369
|
/**
|
|
316
370
|
* @param {string} socketPath - Control socket path.
|
|
317
|
-
* @param {{companion?: Record<string, import("../src/json.js").JsonValue>}} [options] - Optional
|
|
371
|
+
* @param {{companion?: Record<string, import("../src/json.js").JsonValue>, service?: Record<string, import("../src/json.js").JsonValue>}} [options] - Optional dependent processes.
|
|
318
372
|
* @returns {import("../src/config.js").RollbridgeConfig} Normalized config.
|
|
319
373
|
*/
|
|
320
|
-
function buildConfig(socketPath, {companion} = {}) {
|
|
374
|
+
function buildConfig(socketPath, {companion, service} = {}) {
|
|
321
375
|
return normalizeConfig({
|
|
322
376
|
...rawConfig(socketPath),
|
|
323
|
-
...(companion ? {processes: [companion, ...rawConfig(socketPath).processes]} : {})
|
|
377
|
+
...((companion || service) ? {processes: [...(service ? [service] : []), ...(companion ? [companion] : []), ...rawConfig(socketPath).processes]} : {})
|
|
324
378
|
})
|
|
325
379
|
}
|
|
326
380
|
|