Reuse checked bootstrap catalogs and coordinate execution deadlines
This commit is contained in:
@@ -2,7 +2,11 @@ import { graphExecutionRepresentation } from "./graph-execution.js";
|
|||||||
import { lstat } from "node:fs/promises";
|
import { lstat } from "node:fs/promises";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import { hexDigest, ExecutionPlanError, type ExecutionPlan } from "./plan.js";
|
import { hexDigest, ExecutionPlanError, type ExecutionPlan } from "./plan.js";
|
||||||
import { loadExecutionInstallation, type PackageInstallationSelection } from "./installation.js";
|
import {
|
||||||
|
loadExecutionInstallation,
|
||||||
|
createClientContractCatalog,
|
||||||
|
type PackageInstallationSelection,
|
||||||
|
} from "./installation.js";
|
||||||
import {
|
import {
|
||||||
candidateMetadataBudget,
|
candidateMetadataBudget,
|
||||||
readCandidateMetadata,
|
readCandidateMetadata,
|
||||||
@@ -153,6 +157,7 @@ export async function loadCheckedInstallationCandidate(
|
|||||||
programs: artifacts.programs,
|
programs: artifacts.programs,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
const catalog = createClientContractCatalog(plan, installation, scope, kernel);
|
||||||
return {
|
return {
|
||||||
reference,
|
reference,
|
||||||
workspace: plan.definition.workspace.workspaceId,
|
workspace: plan.definition.workspace.workspaceId,
|
||||||
@@ -165,11 +170,15 @@ export async function loadCheckedInstallationCandidate(
|
|||||||
queries,
|
queries,
|
||||||
installation,
|
installation,
|
||||||
executionPlan: plan as ExecutionPlan,
|
executionPlan: plan as ExecutionPlan,
|
||||||
|
clientContractCatalog: catalog.select,
|
||||||
metadata: {
|
metadata: {
|
||||||
get bytes() {
|
get bytes() {
|
||||||
return scope.bytes;
|
return scope.bytes;
|
||||||
},
|
},
|
||||||
release: scope.release,
|
release: () => {
|
||||||
|
catalog.close();
|
||||||
|
scope.release();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -35,8 +35,7 @@ export interface InspectionTemplate {
|
|||||||
}
|
}
|
||||||
/** A finite installed template per interface/lane, not a per-client query compiler.
|
/** A finite installed template per interface/lane, not a per-client query compiler.
|
||||||
* Flags select public cache boundaries; normal scope identity bounds specialization. */
|
* Flags select public cache boundaries; normal scope identity bounds specialization. */
|
||||||
export function compileInspectionTemplates(plan: ExecutionPlan) {
|
export interface InspectionKernel {
|
||||||
const kernel = compileKernelProgram(plan) as {
|
|
||||||
operations: {
|
operations: {
|
||||||
key: string;
|
key: string;
|
||||||
atom: string;
|
atom: string;
|
||||||
@@ -48,7 +47,11 @@ export function compileInspectionTemplates(plan: ExecutionPlan) {
|
|||||||
output: unknown;
|
output: unknown;
|
||||||
binding: { kind: string };
|
binding: { kind: string };
|
||||||
}[];
|
}[];
|
||||||
};
|
}
|
||||||
|
export function compileInspectionTemplates(
|
||||||
|
plan: ExecutionPlan,
|
||||||
|
kernel: InspectionKernel = compileKernelProgram(plan) as InspectionKernel,
|
||||||
|
) {
|
||||||
const queries: unknown[] = [],
|
const queries: unknown[] = [],
|
||||||
contracts: PublicQueryContract[] = [],
|
contracts: PublicQueryContract[] = [],
|
||||||
templates: InspectionTemplate[] = [];
|
templates: InspectionTemplate[] = [];
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { compileInspectionTemplates, type InspectionTemplate } from "./inspection.js";
|
import { compileInspectionTemplates, type InspectionTemplate, type InspectionKernel } from "./inspection.js";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { loadCheckedArtifactWorld } from "./receipts.js";
|
import { loadCheckedArtifactWorld } from "./receipts.js";
|
||||||
import { contentArtifactUrl, loadArtifactWorld, type LoadedArtifactWorld } from "./files.js";
|
import { contentArtifactUrl, loadArtifactWorld, type LoadedArtifactWorld } from "./files.js";
|
||||||
@@ -45,6 +45,7 @@ export function clientContractCatalog(
|
|||||||
plan: ExecutionPlan,
|
plan: ExecutionPlan,
|
||||||
installation: ExecutionInstallation,
|
installation: ExecutionInstallation,
|
||||||
requestedPackages: readonly string[],
|
requestedPackages: readonly string[],
|
||||||
|
checkedKernel?: InspectionKernel,
|
||||||
): ClientContractCatalog {
|
): ClientContractCatalog {
|
||||||
if (installation.plan !== plan.digest || hexDigest(plan.definition) !== plan.digest)
|
if (installation.plan !== plan.digest || hexDigest(plan.definition) !== plan.digest)
|
||||||
throw new ExecutionPlanError(
|
throw new ExecutionPlanError(
|
||||||
@@ -69,7 +70,7 @@ export function clientContractCatalog(
|
|||||||
.map((operation) => publicOperationContract(iface, operation)),
|
.map((operation) => publicOperationContract(iface, operation)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
const inspection = compileInspectionTemplates(plan);
|
const inspection = compileInspectionTemplates(plan, checkedKernel);
|
||||||
return {
|
return {
|
||||||
inspections: inspection.templates,
|
inspections: inspection.templates,
|
||||||
resultInterfaces: Object.fromEntries(
|
resultInterfaces: Object.fromEntries(
|
||||||
@@ -98,6 +99,75 @@ export function clientContractCatalog(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Candidate-owned compiler output, never graph state or authorization. Compile
|
||||||
|
* once, then project each requested package set without hashing/recompiling the
|
||||||
|
* plan. Reserve bounded metadata before compilation; disposal releases both the
|
||||||
|
* catalog and its charge. Callers must supply the candidate's checked inputs. */
|
||||||
|
export function createClientContractCatalog(
|
||||||
|
plan: ExecutionPlan,
|
||||||
|
installation: ExecutionInstallation,
|
||||||
|
metadata: CandidateMetadataScope,
|
||||||
|
checkedKernel?: InspectionKernel,
|
||||||
|
) {
|
||||||
|
const revisions = plan.definition.workspace.packageImports.map((pkg) => pkg.revisionId);
|
||||||
|
const installed = new Set<string>(revisions);
|
||||||
|
let catalog: ClientContractCatalog | undefined;
|
||||||
|
let release: (() => void) | undefined;
|
||||||
|
let closed = false;
|
||||||
|
const freeze = (value: unknown, seen = new WeakSet<object>()) => {
|
||||||
|
if (!value || typeof value !== "object" || seen.has(value)) return;
|
||||||
|
seen.add(value);
|
||||||
|
for (const child of Object.values(value)) freeze(child, seen);
|
||||||
|
Object.freeze(value);
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
select(requestedPackages: readonly string[]): ClientContractCatalog {
|
||||||
|
if (closed) throw Error("CANDIDATE_METADATA_CLOSED");
|
||||||
|
for (const revision of requestedPackages)
|
||||||
|
if (!installed.has(revision))
|
||||||
|
throw new ExecutionPlanError("UNINSTALLED_PACKAGE", revision, "Requested catalog package is not installed");
|
||||||
|
if (!catalog) {
|
||||||
|
// Like other candidate admission charges, this is an allocation estimate,
|
||||||
|
// not a V8 heap limit. The process/cgroup ceiling bounds transient work.
|
||||||
|
const reservation = metadata.reserve(32 * 1024 * 1024);
|
||||||
|
try {
|
||||||
|
const compiled = clientContractCatalog(plan, installation, revisions, checkedKernel);
|
||||||
|
const bytes = Buffer.byteLength(JSON.stringify(compiled));
|
||||||
|
if (bytes > 8 * 1024 * 1024) throw Error("CANDIDATE_CATALOG_LIMIT");
|
||||||
|
freeze(compiled);
|
||||||
|
// No await between reservations: replacing the transient upper bound
|
||||||
|
// with a smaller retained estimate cannot race another admission.
|
||||||
|
reservation();
|
||||||
|
release = metadata.reserve(Math.max(64 * 1024, bytes * 4));
|
||||||
|
catalog = compiled;
|
||||||
|
} catch (error) {
|
||||||
|
reservation();
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const requested = new Set(requestedPackages);
|
||||||
|
return {
|
||||||
|
inspections: catalog.inspections,
|
||||||
|
resultInterfaces: catalog.resultInterfaces,
|
||||||
|
operations: catalog.operations,
|
||||||
|
queries: [
|
||||||
|
...catalog.queries.filter((query) => !installed.has(query.package)),
|
||||||
|
...requestedPackages.flatMap((revision) => catalog!.queries.filter((query) => query.package === revision)),
|
||||||
|
],
|
||||||
|
components: catalog.components.filter((component) => requested.has(component.packageRevision)),
|
||||||
|
moduleBindings: catalog.moduleBindings.filter((binding) => requested.has(binding.packageRevision)),
|
||||||
|
presentations: catalog.presentations.filter((presentation) => requested.has(presentation.packageRevision)),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
closed = true;
|
||||||
|
catalog = undefined;
|
||||||
|
release?.();
|
||||||
|
release = undefined;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export interface PackageInstallationSelection {
|
export interface PackageInstallationSelection {
|
||||||
readonly browser?: { readonly id?: string };
|
readonly browser?: { readonly id?: string };
|
||||||
readonly service?: { readonly id?: string };
|
readonly service?: { readonly id?: string };
|
||||||
|
|||||||
@@ -19,12 +19,16 @@ import {
|
|||||||
expectedArtifactReceipt,
|
expectedArtifactReceipt,
|
||||||
checkExecutionInstallation,
|
checkExecutionInstallation,
|
||||||
clientContractCatalog,
|
clientContractCatalog,
|
||||||
|
createClientContractCatalog,
|
||||||
|
CandidateMetadataBudget,
|
||||||
|
compileKernelProgram,
|
||||||
type ComponentArtifact,
|
type ComponentArtifact,
|
||||||
type PackageArtifacts,
|
type PackageArtifacts,
|
||||||
} from "../src/execution-world/index.js";
|
} from "../src/execution-world/index.js";
|
||||||
import { validateWorkspaceRevision } from "../src/capability-model/index.js";
|
import { validateWorkspaceRevision } from "../src/capability-model/index.js";
|
||||||
import { presentationDeclarations, preparationArtifact } from "../src/react-build/declarations.js";
|
import { presentationDeclarations, preparationArtifact } from "../src/react-build/declarations.js";
|
||||||
import { compileQueryComposition } from "../src/query/composition.js";
|
import { compileQueryComposition } from "../src/query/composition.js";
|
||||||
|
import type { InspectionKernel } from "../src/execution-world/inspection.js";
|
||||||
|
|
||||||
const source = { repository: "https://example.test/components.git", commit: "a".repeat(40) };
|
const source = { repository: "https://example.test/components.git", commit: "a".repeat(40) };
|
||||||
const contract = (name: string) => {
|
const contract = (name: string) => {
|
||||||
@@ -255,6 +259,56 @@ test("checked component-only artifacts install without a service and reject miss
|
|||||||
() => clientContractCatalog(plan, { ...installation, plan: "bad" }, [pkg.revision]),
|
() => clientContractCatalog(plan, { ...installation, plan: "bad" }, [pkg.revision]),
|
||||||
/CATALOG_PLAN_MISMATCH/,
|
/CATALOG_PLAN_MISMATCH/,
|
||||||
);
|
);
|
||||||
|
const budget = new CandidateMetadataBudget();
|
||||||
|
const scope = budget.acquire("catalog-test");
|
||||||
|
const prepared = createClientContractCatalog(
|
||||||
|
plan,
|
||||||
|
installation,
|
||||||
|
scope,
|
||||||
|
compileKernelProgram(plan) as InspectionKernel,
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
assert.throws(() => prepared.select(["absent"]), /UNINSTALLED_PACKAGE/);
|
||||||
|
assert.equal(scope.bytes, 0, "Unknown requests do not allocate compiler metadata");
|
||||||
|
assert.deepEqual(prepared.select([pkg.revision]), catalog);
|
||||||
|
const retained = scope.bytes;
|
||||||
|
assert.ok(retained > 0);
|
||||||
|
assert.ok(retained < 32 * 1024 * 1024, "Small catalogs retain a proportional metadata charge");
|
||||||
|
const full = prepared.select([pkg.revision]);
|
||||||
|
const empty = prepared.select([]);
|
||||||
|
assert.deepEqual(empty.components, []);
|
||||||
|
assert.deepEqual(empty.presentations, []);
|
||||||
|
assert.deepEqual(empty.moduleBindings, []);
|
||||||
|
assert.equal(empty.operations, full.operations, "Shared compiled metadata is reused");
|
||||||
|
assert.equal(Object.isFrozen(full.operations), true);
|
||||||
|
// Any plan access now would reveal a repeated digest/compile on the warm path.
|
||||||
|
const definition = plan.definition;
|
||||||
|
Object.defineProperty(plan, "definition", {
|
||||||
|
configurable: true,
|
||||||
|
get() {
|
||||||
|
throw Error("RECOMPILED_PLAN");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
assert.deepEqual(prepared.select([pkg.revision]), full);
|
||||||
|
assert.equal(scope.bytes, retained, "Package projections do not accumulate cache entries");
|
||||||
|
} finally {
|
||||||
|
Object.defineProperty(plan, "definition", { configurable: true, value: definition });
|
||||||
|
}
|
||||||
|
prepared.close();
|
||||||
|
assert.equal(scope.bytes, 0);
|
||||||
|
assert.throws(() => prepared.select([pkg.revision]), /CANDIDATE_METADATA_CLOSED/);
|
||||||
|
} finally {
|
||||||
|
prepared.close();
|
||||||
|
scope.release();
|
||||||
|
}
|
||||||
|
const tight = new CandidateMetadataBudget({ bytes: 1000, ownerBytes: 1000, candidates: 1 });
|
||||||
|
const tightScope = tight.acquire("catalog-test");
|
||||||
|
const refused = createClientContractCatalog(plan, installation, tightScope);
|
||||||
|
assert.throws(() => refused.select([pkg.revision]), /CANDIDATE_METADATA_LIMIT/);
|
||||||
|
assert.equal(tightScope.bytes, 0);
|
||||||
|
refused.close();
|
||||||
|
tightScope.release();
|
||||||
} finally {
|
} finally {
|
||||||
await rm(root, { recursive: true, force: true });
|
await rm(root, { recursive: true, force: true });
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user