chore(vendor): import pi 1.3.0
Imported as a linear vendor commit so forge rebases do not replay the upstream root tree. git-subtree-dir: third_party/pi git-subtree-split: 71023aec1546d6d677a65669c5665c0927ce0dd0
This commit is contained in:
1 parent
719370c8f7
commit
6081e3e325
1743 files changed
+205459
-131704
No files matched your search
@@ -0,0 +1,73 @@
|
||||
// A link wrapper that counts what crosses it, per direction: frames by type and bytes (JSON text length). It can also
|
||||
// hold back the acknowledgements its own side sends (`paused`), to fill the peer's flow-control window.
|
||||
import type { Frame, Link } from "../../src/index.ts";
|
||||
|
||||
export class CountingLink implements Link {
|
||||
readonly inner: Link;
|
||||
/** Frames this side sent, in order. */
|
||||
readonly sent: Frame[] = [];
|
||||
/** Frames this side received, in order. */
|
||||
readonly received: Frame[] = [];
|
||||
sentBytes = 0;
|
||||
receivedBytes = 0;
|
||||
/** Acknowledgements are held back (the peer's window fills up). */
|
||||
paused = false;
|
||||
#held: Frame | undefined;
|
||||
|
||||
constructor(inner: Link) {
|
||||
this.inner = inner;
|
||||
}
|
||||
|
||||
get closed(): boolean {
|
||||
return this.inner.closed;
|
||||
}
|
||||
|
||||
send(frame: Frame): number | undefined {
|
||||
if (frame.t === "ack" && this.paused) {
|
||||
this.#held = frame;
|
||||
return 0;
|
||||
}
|
||||
this.sent.push(frame);
|
||||
this.sentBytes += JSON.stringify(frame).length;
|
||||
return this.inner.send(frame);
|
||||
}
|
||||
|
||||
/** Lets the held acknowledgement through and stops holding them. */
|
||||
release(): void {
|
||||
this.paused = false;
|
||||
const held = this.#held;
|
||||
this.#held = undefined;
|
||||
if (held !== undefined) this.inner.send(held);
|
||||
}
|
||||
|
||||
receive(handler: (frame: Frame) => void): void {
|
||||
this.inner.receive((frame) => {
|
||||
this.received.push(frame);
|
||||
this.receivedBytes += JSON.stringify(frame).length;
|
||||
handler(frame);
|
||||
});
|
||||
}
|
||||
|
||||
onClose(handler: (reason: string) => void): void {
|
||||
this.inner.onClose(handler);
|
||||
}
|
||||
|
||||
close(reason?: string): void {
|
||||
this.inner.close(reason);
|
||||
}
|
||||
|
||||
/** Frames of type `t` this side sent (`sent`) or received (`received`). */
|
||||
count(direction: "sent" | "received", t: Frame["t"]): number {
|
||||
return this[direction].filter((frame) => frame.t === t).length;
|
||||
}
|
||||
|
||||
/** Frames this side sent or received, except pings, pongs and acknowledgements. */
|
||||
traffic(direction: "sent" | "received"): Frame[] {
|
||||
return this[direction].filter((frame) => frame.t !== "ping" && frame.t !== "pong" && frame.t !== "ack");
|
||||
}
|
||||
}
|
||||
|
||||
export type StateFrame = Extract<Frame, { t: "state" }>;
|
||||
|
||||
export const stateFrames = (frames: readonly Frame[]): StateFrame[] =>
|
||||
frames.filter((frame): frame is StateFrame => frame.t === "state");
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Type } from "typebox";
|
||||
import { hostNamed, method, remote } from "../../src/index.ts";
|
||||
|
||||
export const Echo = remote("t.echo", {
|
||||
version: 1,
|
||||
input: Type.Object({}),
|
||||
host: hostNamed("w1"),
|
||||
variants: {
|
||||
main: { methods: { echo: method(Type.String(), Type.String()), pid: method(undefined, Type.Number()) } },
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,81 @@
|
||||
// Runs the node loader outside vitest: its module runner bypasses the Node resolve hooks the loader installs.
|
||||
import assert from "node:assert/strict";
|
||||
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { Type } from "typebox";
|
||||
import { createHost, local, method } from "../../src/index.ts";
|
||||
import { nodeLoader } from "../../src/loader.ts";
|
||||
|
||||
// Plugin modules live under the package so bare imports (typebox) resolve; removed afterwards.
|
||||
const dir = fileURLToPath(new URL(`../.tmp-plugins-${process.pid}/`, import.meta.url));
|
||||
// Separate from `dir`, which becomes a reload root once its facet loads.
|
||||
const plugins = fileURLToPath(new URL(`../.tmp-plugins-${process.pid}-order/`, import.meta.url));
|
||||
const chord = new URL("../../src/index.ts", import.meta.url).href;
|
||||
|
||||
// The test's own copy of the plugin's contract: binding is by description, not object identity.
|
||||
const Hello = local("t.hello", {
|
||||
version: 1,
|
||||
input: Type.Object({}),
|
||||
members: { greet: method(Type.String(), Type.String()) },
|
||||
});
|
||||
|
||||
const facetSource = `
|
||||
import { Type } from "typebox";
|
||||
import { defineFacet, local, method } from ${JSON.stringify(chord)};
|
||||
import { greeting } from "./greeting.ts";
|
||||
|
||||
const Hello = local("t.hello", {
|
||||
version: 1,
|
||||
input: Type.Object({}),
|
||||
members: { greet: method(Type.String(), Type.String()) },
|
||||
});
|
||||
|
||||
export default defineFacet((host) => {
|
||||
host.serve(Hello, () => ({ greet: (name) => greeting + " " + name }));
|
||||
});
|
||||
`;
|
||||
|
||||
const writeGreeting = (text: string) =>
|
||||
writeFileSync(`${dir}greeting.ts`, `export const greeting = ${JSON.stringify(text)};\n`);
|
||||
|
||||
try {
|
||||
mkdirSync(dir, { recursive: true });
|
||||
writeFileSync(`${dir}facet.ts`, facetSource);
|
||||
writeGreeting("v1");
|
||||
const host = createHost({ self: { kind: "host" }, loader: nodeLoader, onError: () => {} });
|
||||
await host.load([`${dir}facet.ts`]);
|
||||
const ref = host.hold(Hello, {});
|
||||
assert.equal(await ref.greet("x"), "v1 x");
|
||||
writeGreeting("v2");
|
||||
const report = await host.reload([`${dir}facet.ts`]);
|
||||
assert.deepEqual(report.changed, ["t.hello"]);
|
||||
assert.equal(await ref.greet("x"), "v2 x");
|
||||
await host.close();
|
||||
|
||||
// A facet listed first that imports a later facet's directory shares that facet's copy of the module.
|
||||
const Shared = local("t.shared", {
|
||||
version: 1,
|
||||
input: Type.Object({}),
|
||||
members: { same: method(undefined, Type.Boolean()) },
|
||||
});
|
||||
mkdirSync(`${plugins}a`, { recursive: true });
|
||||
mkdirSync(`${plugins}b`, { recursive: true });
|
||||
writeFileSync(`${plugins}b/state.ts`, "export const state = {};\n");
|
||||
writeFileSync(
|
||||
`${plugins}b/facet.ts`,
|
||||
`import { defineFacet } from ${JSON.stringify(chord)};\nimport { state } from "./state.ts";\nexport const own = state;\nexport default defineFacet(() => {});\n`,
|
||||
);
|
||||
writeFileSync(
|
||||
`${plugins}a/facet.ts`,
|
||||
`import { Type } from "typebox";\nimport { defineFacet, local, method } from ${JSON.stringify(chord)};\nimport { state } from "../b/state.ts";\n` +
|
||||
`const Shared = local("t.shared", { version: 1, input: Type.Object({}), members: { same: method(undefined, Type.Boolean()) } });\n` +
|
||||
`export default defineFacet((host) => host.serve(Shared, () => ({ same: async () => (await import(new URL("../b/facet.ts", import.meta.url).href)).own === state })));\n`,
|
||||
);
|
||||
const ordered = createHost({ self: { kind: "host" }, loader: nodeLoader, onError: () => {} });
|
||||
await ordered.load([`${plugins}a/facet.ts`, `${plugins}b/facet.ts`]);
|
||||
assert.equal(await ordered.hold(Shared, {}).same(), true);
|
||||
await ordered.close();
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
rmSync(plugins, { recursive: true, force: true });
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// Child host for manager tests. FIXTURE_MODE: normal | ignore-shutdown | fatal.
|
||||
import { spawn } from "node:child_process";
|
||||
import { writeFileSync } from "node:fs";
|
||||
import { createHost, type IpcChannel, ipcLink } from "../../src/index.ts";
|
||||
import { childControl } from "../../src/manager.ts";
|
||||
import { Echo } from "./echo.ts";
|
||||
|
||||
const mode = process.env.FIXTURE_MODE ?? "normal";
|
||||
const control = childControl();
|
||||
if (mode === "fatal") {
|
||||
control.fatal(new Error("fixture refuses to start"));
|
||||
setTimeout(() => process.exit(1), 200);
|
||||
} else {
|
||||
// Before anything that owns data: the kernel lock (if the manager configured one).
|
||||
await control.lock().catch(() => {
|
||||
setTimeout(() => process.exit(1), 200);
|
||||
return new Promise<never>(() => {});
|
||||
});
|
||||
const host = createHost({
|
||||
self: { kind: "host" },
|
||||
name: process.env.HOST_NAME ?? "worker",
|
||||
upstream: { link: ipcLink(process as unknown as IpcChannel), principal: { kind: "server" } },
|
||||
onError: () => {},
|
||||
});
|
||||
const init = (await control.init) as { greeting: string };
|
||||
host.serve(Echo, () => ({
|
||||
variant: "main",
|
||||
echo: (text: string) => `${init.greeting} ${text} from ${process.pid}`,
|
||||
pid: () => process.pid,
|
||||
}));
|
||||
if (process.env.GRANDCHILD_FILE) {
|
||||
const grandchild = spawn(process.execPath, ["-e", "setInterval(() => {}, 1000)"], { stdio: "ignore" });
|
||||
writeFileSync(process.env.GRANDCHILD_FILE, String(grandchild.pid));
|
||||
}
|
||||
control.onShutdown(async () => {
|
||||
if (mode === "ignore-shutdown") return;
|
||||
await host.close();
|
||||
process.exit(0);
|
||||
});
|
||||
control.ready();
|
||||
}
|
||||
Reference in new issue
Block a user