Skip to content

Commit 1f4e3b6

Browse files
committed
test(authority): keep the crash worker alive until SIGKILL
The restore crash worker awaited a bare promise after its durable boundary. A pending promise does not hold the event loop, so the child could exit 13 ("unfinished top-level await") before the parent delivered SIGKILL, and the File case then asserted null instead of a signal. Pin the IPC channel with the same message-listener barrier the task-lease crash worker uses, assert that SIGKILL was actually delivered, and hold the parent 250ms past the durable message so the late-signal race stays covered. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
1 parent 831ac3b commit 1f4e3b6

2 files changed

Lines changed: 16 additions & 3 deletions

File tree

‎tests/control_plane_ts/authority_archive_crash.test.ts‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ import {SqliteAuthorityStore} from "../../loopx/control_plane/coordination/sqlit
1010
import {exportAuthorityArchive, restoreAuthorityArchive} from "../../loopx/control_plane/coordination/authority_archive.ts";
1111
import {auditAuthorityArchive} from "../../loopx/control_plane/coordination/authority_archive_audit.ts";
1212

13+
// The worker must survive the durable boundary until this late SIGKILL. An
14+
// immediate signal hides a worker that already exited 13 ("unfinished
15+
// top-level await") because its IPC channel stopped holding the event loop.
16+
const crashSignalDelayMs = 250;
17+
1318
for (const provider of ["file", "sqlite"] as const) {
1419
test(`${provider}: killed after checkpoint commit; reopen, resume, audit and continue CAS`, {timeout: 60000}, async () => {
1520
const root = await mkdtemp(join(tmpdir(), "archive-crash-"));
@@ -38,7 +43,8 @@ for (const provider of ["file", "sqlite"] as const) {
3843
const ended = once(worker, "exit");
3944
const notification = await Promise.race([boundary, ended.then(() => { throw new Error(`worker exited before crash: ${stderr}`); })]);
4045
assert.deepEqual(notification[0], {status: "durable", cursor: "65"});
41-
worker.kill("SIGKILL");
46+
await new Promise(resolve => setTimeout(resolve, crashSignalDelayMs));
47+
assert.equal(worker.kill("SIGKILL"), true, "the crash worker was still alive to receive SIGKILL");
4248
const exit = await ended;
4349
assert.equal(exit[1], "SIGKILL");
4450
const reopened = provider === "sqlite" ? new SqliteAuthorityStore(destination, "goal", {existingOnly: true})

‎tests/control_plane_ts/authority_archive_restore_process.ts‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ import {restoreAuthorityArchive} from "../../loopx/control_plane/coordination/au
55
import type {AuthorityStore} from "../../loopx/control_plane/coordination/authority_store.ts";
66
const [archive, target, digest, kind, stopAt] = process.argv.slice(2);
77
const store = kind === "sqlite" ? new SqliteAuthorityStore(target, "goal") : new FileAuthorityStore(target, "goal");
8+
// Hold the IPC channel across the durable boundary. A pending promise alone
9+
// leaves the event loop empty, so the child exits 13 ("unfinished top-level
10+
// await") before the parent can deliver SIGKILL. The message listener pins the
11+
// channel, the same barrier the task-lease crash worker uses.
12+
let resume: () => void = () => {};
13+
const crashBarrier = new Promise<void>(resolve => {resume = resolve;});
14+
process.on("message", () => resume());
815
const interrupted: AuthorityStore = {
916
providerKind: store.providerKind,
1017
storeIdentity: () => store.storeIdentity(), loadAuthority: () => store.loadAuthority(),
@@ -13,8 +20,8 @@ const interrupted: AuthorityStore = {
1320
const committed = await store.commitAuthority(request);
1421
if (committed.status === "applied" && committed.cursor === stopAt) {
1522
process.send!({status: "durable", cursor: committed.cursor});
16-
// IPC keeps the worker alive until the parent sends SIGKILL.
17-
await new Promise<never>(() => {});
23+
// The parent requires the named crash boundary and signals SIGKILL here.
24+
await crashBarrier;
1825
}
1926
return committed;
2027
},

0 commit comments

Comments
 (0)