mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
createWorkers bundles the worker entry once (esbuild, Node builtins and installed packages external) and forks every worker from that snapshot, so an edit of the app's source while the server runs reaches workers only through a restart. The kit watches the bundle's inputs as base code. p2 creates its session workers with SessionWorkers.create and keeps the worker base's inputs as a dev restart baseline.
67 lines
2.9 KiB
TypeScript
67 lines
2.9 KiB
TypeScript
// A worker crash (slice 10): workers never restart by themselves. A crashed worker's address stays failed, its
|
|
// holders see `failed` with the error, and nothing forks until the app retries (a Retry button) or a rollout ships new
|
|
// code. Here: a session crashes, stays failed for a while, then Retry brings it back.
|
|
// Run from packages/chord:
|
|
// node --conditions=source --experimental-strip-types test/examples/42-worker-crash/main.ts
|
|
import { fileURLToPath } from "node:url";
|
|
import { BACKGROUND_CONTEXT as context } from "../../../src/context/index.ts";
|
|
import { hostKind } from "../../../src/contracts/index.ts";
|
|
import { createHost } from "../../../src/host/index.ts";
|
|
import { createPlugins } from "../../../src/node/plugins/index.ts";
|
|
import { createWorkers } from "../../../src/node/workers/index.ts";
|
|
import { Process, Session } from "./plugins/sessions/contract.ts";
|
|
|
|
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
|
|
const plugins = createPlugins(
|
|
{
|
|
roots: [fileURLToPath(new URL("./plugins/", import.meta.url))],
|
|
kinds: { worker: { platform: "node", hostPackages: ["@earendil-works/chord"] } },
|
|
onError: console.error,
|
|
},
|
|
context,
|
|
);
|
|
const server = createHost({ address: hostKind("server")(), principal: { kind: "server" }, onError: () => {} });
|
|
const workers = await createWorkers(
|
|
server,
|
|
Session,
|
|
{
|
|
kind: "worker",
|
|
entry: fileURLToPath(new URL("./worker.ts", import.meta.url)),
|
|
execArgv: ["--conditions=source", "--experimental-strip-types", "--no-warnings"],
|
|
authorize: () => true,
|
|
principal: (address) => ({ kind: "worker", session: address.params.id }),
|
|
start: () => ({}),
|
|
lock: (address) => `example-42:${process.pid}:${address.key}`,
|
|
plugins,
|
|
onError: (error) => console.log(` reported: ${error.kind}`),
|
|
},
|
|
context,
|
|
);
|
|
await server.load([], context);
|
|
|
|
// 1. A held session (a tab showing it): its worker runs.
|
|
const session = server.hold(Process, { id: "S1" }, context);
|
|
const state = () => session.state.value.kind;
|
|
while (state() !== "live") await sleep(10);
|
|
console.log(`1. S1 runs in process ${session.pid.value}`);
|
|
|
|
// 2. It crashes. The address is failed, and the holder sees why; it stays on the address's path.
|
|
await session.crash(context);
|
|
while (state() !== "failed") await sleep(10);
|
|
const failed = workers.state.value["session|S1"];
|
|
console.log(`2. crashed: ${failed?.status === "failed" ? failed.error.message : failed?.status}`);
|
|
|
|
// 3. No restart loop: a second later it is still failed, and no other worker was started.
|
|
await sleep(1_000);
|
|
console.log(`3. a second later: ${workers.state.value["session|S1"]?.status}, the holder: ${state()}`);
|
|
|
|
// 4. Retry (the app's button): the worker starts again and the same holder binds to it.
|
|
await workers.retry(Session({ id: "S1" }), context);
|
|
while (state() !== "live") await sleep(10);
|
|
console.log(`4. after Retry: S1 runs in process ${session.pid.value}`);
|
|
|
|
session.release();
|
|
await workers.close(context);
|
|
await server.close(context);
|
|
plugins.close();
|