mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
fix(chord): nodeLoader registers every facet root before importing any
A facet listed before another facet whose directory it imports from loaded a second copy of that module; register all roots first so every facet of one load shares one copy, whatever the order.
This commit is contained in:
parent
d6370b72d0
commit
7d75a0e3eb
3 changed files with 34 additions and 2 deletions
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
- `nodeLoader` registers every facet's directory before importing any, so a facet that imports a file of a facet listed after it gets the same module copy as that facet; before, the load order could produce duplicates.
|
||||||
- A link that is already closed when a host accepts or routes to it is no longer kept as a connection: before, a route that returned a dead link (a child process that exited before its link was wrapped) made every later call to that host name fail with `unavailable` until restart, and leaked the connection's ping timer.
|
- A link that is already closed when a host accepts or routes to it is no longer kept as a connection: before, a route that returned a dead link (a child process that exited before its link was wrapped) made every later call to that host name fail with `unavailable` until restart, and leaked the connection's ping timer.
|
||||||
- Fixed `processLock()` failing with `listen EINVAL` on macOS and the BSDs when the temporary directory is too deep for a socket path (sun_path holds 104 bytes); the lock socket then lives in a private per-user directory, `/tmp/chord2-<uid>`
|
- Fixed `processLock()` failing with `listen EINVAL` on macOS and the BSDs when the temporary directory is too deep for a socket path (sun_path holds 104 bytes); the lock socket then lives in a private per-user directory, `/tmp/chord2-<uid>`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,11 +45,15 @@ export const nodeLoader: FacetLoader = async (modules) => {
|
||||||
hook();
|
hook();
|
||||||
const version = String(++generation);
|
const version = String(++generation);
|
||||||
const facets: Facet[] = [];
|
const facets: Facet[] = [];
|
||||||
for (const module of modules) {
|
const urls = modules.map((module) => {
|
||||||
const url = toUrl(module);
|
const url = toUrl(module);
|
||||||
url.search = "";
|
url.search = "";
|
||||||
|
return url;
|
||||||
|
});
|
||||||
|
// Every root first: a facet importing another facet's directory gets this load's copy, whatever the order.
|
||||||
|
for (const url of urls) roots.add(new URL(".", url).href);
|
||||||
|
for (const url of urls) {
|
||||||
const name = url.href;
|
const name = url.href;
|
||||||
roots.add(new URL(".", url).href);
|
|
||||||
url.searchParams.set(PARAM, version);
|
url.searchParams.set(PARAM, version);
|
||||||
// Owner-approved exception to the no-dynamic-import rule; see TODO(loader) above.
|
// Owner-approved exception to the no-dynamic-import rule; see TODO(loader) above.
|
||||||
const loaded = (await import(url.href)) as { default?: FacetDefinition };
|
const loaded = (await import(url.href)) as { default?: FacetDefinition };
|
||||||
|
|
|
||||||
27
packages/chord/test/fixtures/loader.ts
vendored
27
packages/chord/test/fixtures/loader.ts
vendored
|
|
@ -8,6 +8,8 @@ import { nodeLoader } from "../../src/loader.ts";
|
||||||
|
|
||||||
// Plugin modules live under the package so bare imports (typebox) resolve; removed afterwards.
|
// 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));
|
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;
|
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.
|
// The test's own copy of the plugin's contract: binding is by description, not object identity.
|
||||||
|
|
@ -49,6 +51,31 @@ try {
|
||||||
assert.deepEqual(report.changed, ["t.hello"]);
|
assert.deepEqual(report.changed, ["t.hello"]);
|
||||||
assert.equal(await handle.greet("x"), "v2 x");
|
assert.equal(await handle.greet("x"), "v2 x");
|
||||||
await host.close();
|
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(Type.Null(), 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(Type.Null(), 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({ loader: nodeLoader, onError: () => {} });
|
||||||
|
await ordered.load([`${plugins}a/facet.ts`, `${plugins}b/facet.ts`]);
|
||||||
|
assert.equal(await ordered.get(Shared, {}).same(null), true);
|
||||||
|
await ordered.close();
|
||||||
} finally {
|
} finally {
|
||||||
rmSync(dir, { recursive: true, force: true });
|
rmSync(dir, { recursive: true, force: true });
|
||||||
|
rmSync(plugins, { recursive: true, force: true });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue