File size: 3,024 Bytes
f778c12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
// Process-boundary proof that the hard-exit watchdog survives main-thread starvation.
import { spawnSync } from "node:child_process";
import path from "node:path";
import { pathToFileURL } from "node:url";
import { expect, it } from "vitest";

const WATCHDOG_DELAY_MS = 100;
const CHILD_TIMEOUT_MS = 3_000;
const watchdogModuleUrl = pathToFileURL(
  path.resolve("src/cli/gateway-cli/shutdown-hard-exit.ts"),
).href;

function runWatchdogChild() {
  const script = `
    import { writeSync } from "node:fs";
    import { armShutdownHardExitWatchdog } from ${JSON.stringify(watchdogModuleUrl)};

    const fail = (message) => {
      writeSync(2, \`error:\${message}\\n\`);
      process.exit(2);
    };
    const firstWatchdog = armShutdownHardExitWatchdog({
      delayMs: ${WATCHDOG_DELAY_MS},
      onError: (error) => fail(String(error)),
    });
    if (firstWatchdog === null) {
      fail("first-watchdog-not-armed");
    }
    firstWatchdog.cancel();
    await new Promise((resolve) => setTimeout(resolve, ${WATCHDOG_DELAY_MS * 2}));
    writeSync(1, "cancelled-watchdog-deadline-survived\\n");

    process.once("beforeExit", () => {
      // Reaching beforeExit proves the cancelled worker no longer retains process liveness.
      writeSync(1, "cancelled-watchdog-released-liveness\\n");
      const secondWatchdog = armShutdownHardExitWatchdog({
        delayMs: ${WATCHDOG_DELAY_MS},
        onError: (error) => fail(String(error)),
      });
      if (secondWatchdog === null) {
        fail("second-watchdog-not-armed");
      }
      writeSync(1, "second-watchdog-armed\\n");
      while (true) {}
    });
  `;
  const startedAt = Date.now();
  const result = spawnSync(
    process.execPath,
    ["--import", "tsx", "--input-type=module", "--eval", script],
    {
      cwd: path.resolve("."),
      encoding: "utf8",
      env: { ...process.env, VITEST: undefined },
      killSignal: "SIGKILL",
      timeout: CHILD_TIMEOUT_MS,
    },
  );
  return { elapsedMs: Date.now() - startedAt, result };
}

it("cancels one watchdog before another kills a blocked main thread", () => {
  const { elapsedMs, result } = runWatchdogChild();
  const diagnostics = JSON.stringify(
    {
      elapsedMs,
      status: result.status,
      signal: result.signal,
      error: result.error ? String(result.error) : undefined,
      stdout: result.stdout,
      stderr: result.stderr,
    },
    null,
    2,
  );
  const markers = [
    "cancelled-watchdog-deadline-survived",
    "cancelled-watchdog-released-liveness",
    "second-watchdog-armed",
  ];
  let previousMarkerIndex = -1;
  for (const marker of markers) {
    const markerIndex = result.stdout.indexOf(marker);
    expect(markerIndex, diagnostics).toBeGreaterThan(previousMarkerIndex);
    previousMarkerIndex = markerIndex;
  }

  expect(result.error, diagnostics).toBeUndefined();
  expect(result.status, diagnostics).toBeNull();
  expect(result.signal, diagnostics).toBe("SIGKILL");
  expect(elapsedMs, diagnostics).toBeLessThan(CHILD_TIMEOUT_MS);
});