Keep checked execution groups and public catalog preparation cooperative
This commit is contained in:
@@ -3,7 +3,7 @@ import path from "node:path";
|
||||
import { loadCheckedArtifactWorld } from "./receipts.js";
|
||||
import { contentArtifactUrl, loadArtifactWorld, type LoadedArtifactWorld } from "./files.js";
|
||||
import { selectArtifactImplementation, type ArtifactImplementation, type ComponentArtifact } from "./artifacts.js";
|
||||
import { ExecutionPlanError, hexDigest, type ExecutionPlan } from "./plan.js";
|
||||
import { ExecutionPlanError, hexDigestAsync, type ExecutionPlan } from "./plan.js";
|
||||
import {
|
||||
publicOperationContract,
|
||||
publicQueryContract,
|
||||
@@ -41,13 +41,13 @@ export interface ClientContractCatalog {
|
||||
}[];
|
||||
}
|
||||
/** Explicit projection prevents accidental spreading of filesystem roots or private bindings. */
|
||||
export function clientContractCatalog(
|
||||
export async function clientContractCatalog(
|
||||
plan: ExecutionPlan,
|
||||
installation: ExecutionInstallation,
|
||||
requestedPackages: readonly string[],
|
||||
checkedKernel?: InspectionKernel,
|
||||
): ClientContractCatalog {
|
||||
if (installation.plan !== plan.digest || hexDigest(plan.definition) !== plan.digest)
|
||||
): Promise<ClientContractCatalog> {
|
||||
if (installation.plan !== plan.digest || (await hexDigestAsync(plan.definition)) !== plan.digest)
|
||||
throw new ExecutionPlanError(
|
||||
"CATALOG_PLAN_MISMATCH",
|
||||
plan.digest,
|
||||
@@ -112,6 +112,7 @@ export function createClientContractCatalog(
|
||||
const revisions = plan.definition.workspace.packageImports.map((pkg) => pkg.revisionId);
|
||||
const installed = new Set<string>(revisions);
|
||||
let catalog: ClientContractCatalog | undefined;
|
||||
let preparing: Promise<void> | undefined;
|
||||
let release: (() => void) | undefined;
|
||||
let closed = false;
|
||||
const freeze = (value: unknown, seen = new WeakSet<object>()) => {
|
||||
@@ -121,7 +122,7 @@ export function createClientContractCatalog(
|
||||
Object.freeze(value);
|
||||
};
|
||||
return {
|
||||
select(requestedPackages: readonly string[]): ClientContractCatalog {
|
||||
async select(requestedPackages: readonly string[]): Promise<ClientContractCatalog> {
|
||||
if (closed) throw Error("CANDIDATE_METADATA_CLOSED");
|
||||
for (const revision of requestedPackages)
|
||||
if (!installed.has(revision))
|
||||
@@ -129,22 +130,29 @@ export function createClientContractCatalog(
|
||||
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;
|
||||
}
|
||||
preparing ??= (async () => {
|
||||
const reservation = metadata.reserve(32 * 1024 * 1024);
|
||||
try {
|
||||
const compiled = await clientContractCatalog(plan, installation, revisions, checkedKernel);
|
||||
if (closed) throw Error("CANDIDATE_METADATA_CLOSED");
|
||||
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;
|
||||
}
|
||||
})().finally(() => {
|
||||
preparing = undefined;
|
||||
});
|
||||
await preparing;
|
||||
}
|
||||
if (closed || !catalog) throw Error("CANDIDATE_METADATA_CLOSED");
|
||||
const requested = new Set(requestedPackages);
|
||||
return {
|
||||
inspections: catalog.inspections,
|
||||
|
||||
Reference in New Issue
Block a user