mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
A worker killed by a signal left its lock's socket file, and every later start of its address failed closed on it, retry too. The workers component now removes a lock socket file nobody accepts on before each start and after a child's exit by a signal, in the address's queue. processLock also treats ENOTSOCK as stale instead of retrying until its context ended.
89 lines
3.7 KiB
TypeScript
89 lines
3.7 KiB
TypeScript
// Tests of the worker's side (slice 10 §3): the start message, the lock, `started` on `connect`, `ready`; shutdown
|
|
// during startup, the parent's disconnect and SIGTERM each run the closers and exit; `fatal` redacted.
|
|
|
|
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { describe, expect, it } from "vitest";
|
|
import { hostKind, str } from "../../../src/contracts/index.ts";
|
|
import type { FromWorker } from "../../../src/node/workers/control.ts";
|
|
import { type Exit, WorkerProcess } from "../../../src/node/workers/process.ts";
|
|
import type { WorkerBehaviour } from "../fixtures/workers/control.ts";
|
|
import { until } from "./support.ts";
|
|
|
|
const ENTRY = fileURLToPath(new URL("../fixtures/workers/worker.ts", import.meta.url));
|
|
|
|
const Session = hostKind("session", { id: str() });
|
|
|
|
/** The fixture worker forked and started with `behaviour`; its control messages and exit collected. */
|
|
function started(behaviour: WorkerBehaviour) {
|
|
const control: FromWorker[] = [];
|
|
let exit: Exit | undefined;
|
|
const child = new WorkerProcess(
|
|
{
|
|
entry: ENTRY,
|
|
execArgv: ["--conditions=source", "--experimental-strip-types", "--no-warnings"],
|
|
onOutput: () => {},
|
|
},
|
|
{
|
|
control: (message) => control.push(message),
|
|
linkLost: () => {},
|
|
linkClosed: () => {},
|
|
exited: (value) => {
|
|
exit = value;
|
|
},
|
|
},
|
|
);
|
|
child.send({
|
|
kind: "start",
|
|
address: Session({ id: "S1" }),
|
|
start: behaviour,
|
|
scripts: [],
|
|
lock: `chord-test-main:${process.pid}:${Date.now()}:${Math.random()}`,
|
|
});
|
|
return { child, control, exited: () => exit };
|
|
}
|
|
|
|
describe("the worker's side", () => {
|
|
it("sends started when the app connects, then ready with its load report and idle state", async () => {
|
|
const { child, control } = started({});
|
|
await until(() => control.some(({ kind }) => kind === "ready"));
|
|
expect(control.map(({ kind }) => kind)).toEqual(["started", "ready"]);
|
|
expect(control[1]).toMatchObject({ idle: true, report: { plugins: { added: [] }, failed: [] } });
|
|
// Shut down, not killed: a SIGKILL would leave its lock's socket file on macOS and the BSDs.
|
|
child.send({ kind: "shutdown" });
|
|
await child.exited;
|
|
});
|
|
|
|
it("shuts down during its startup: aborts it, runs the closers registered so far in reverse order, exits 0", async () => {
|
|
const dir = await mkdtemp(join(tmpdir(), "chord-main-"));
|
|
const { child, control, exited } = started({ hang: true, log: join(dir, "log") });
|
|
await until(() => control.some(({ kind }) => kind === "started"));
|
|
child.send({ kind: "shutdown" });
|
|
await until(() => exited() !== undefined);
|
|
expect(exited()).toEqual({ code: 0, signal: null });
|
|
expect(await readFile(join(dir, "log"), "utf8")).toBe("store\nhost\n");
|
|
await rm(dir, { recursive: true, force: true });
|
|
});
|
|
|
|
it("shuts down on SIGTERM and on the parent's disconnect", async () => {
|
|
const term = started({});
|
|
await until(() => term.control.some(({ kind }) => kind === "ready"));
|
|
process.kill(term.child.pid, "SIGTERM");
|
|
await until(() => term.exited() !== undefined);
|
|
expect(term.exited()).toEqual({ code: 0, signal: null });
|
|
const gone = started({});
|
|
await until(() => gone.control.some(({ kind }) => kind === "ready"));
|
|
gone.child.link.close();
|
|
await until(() => gone.exited() !== undefined);
|
|
expect(gone.exited()).toEqual({ code: 0, signal: null });
|
|
});
|
|
|
|
it("sends fatal with the redacted error and exits 1", async () => {
|
|
const { control, exited } = started({ fatal: "no secret-key-42 here" });
|
|
await until(() => exited() !== undefined);
|
|
expect(control).toEqual([{ kind: "fatal", error: { message: "no [redacted] here" } }]);
|
|
expect(exited()?.code).toBe(1);
|
|
});
|
|
});
|