Keep cold Candidate verification cooperative with controller renewals
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { setImmediate } from "node:timers/promises";
|
||||
import type { Binding, Conformance, DependencyBinding, PersistentAttachment, WorkspaceRevision } from "./types.js";
|
||||
import { validateWorkspaceRevision } from "./validation.js";
|
||||
|
||||
@@ -21,6 +22,52 @@ export const canonicalJson = (value: unknown): string => {
|
||||
};
|
||||
export const contentDigest = (value: unknown) =>
|
||||
`sha256:${createHash("sha256").update(canonicalJson(value)).digest("hex")}`;
|
||||
/** Same canonical bytes as contentDigest, without monopolizing a live host's
|
||||
* event loop while verifying large immutable metadata. No validation is skipped.
|
||||
* This is for asynchronous loading, not a different build/wire identity. */
|
||||
export async function contentDigestAsync(value: unknown): Promise<string> {
|
||||
function* parts(value: unknown): Generator<string> {
|
||||
if (Array.isArray(value)) {
|
||||
yield "[";
|
||||
for (let index = 0; index < value.length; index++) {
|
||||
if (index) yield ",";
|
||||
// Match canonicalJson's Array.map behavior for sparse arrays too.
|
||||
if (index in value) yield* parts(value[index]);
|
||||
}
|
||||
yield "]";
|
||||
} else if (value !== null && typeof value === "object") {
|
||||
if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null)
|
||||
throw new Error("Expected a plain JSON object");
|
||||
yield "{";
|
||||
let first = true;
|
||||
for (const key of Object.keys(value).sort(compareText)) {
|
||||
const entry = (value as Record<string, unknown>)[key];
|
||||
if (entry === undefined) continue;
|
||||
if (!first) yield ",";
|
||||
first = false;
|
||||
yield JSON.stringify(key) + ":";
|
||||
yield* parts(entry);
|
||||
}
|
||||
yield "}";
|
||||
} else yield canonicalJson(value);
|
||||
}
|
||||
const hash = createHash("sha256");
|
||||
let buffer = "",
|
||||
sliceStarted = performance.now();
|
||||
for (const part of parts(value)) {
|
||||
buffer += part;
|
||||
if (buffer.length >= 65536) {
|
||||
hash.update(buffer);
|
||||
buffer = "";
|
||||
if (performance.now() - sliceStarted >= 8) {
|
||||
await setImmediate();
|
||||
sliceStarted = performance.now();
|
||||
}
|
||||
}
|
||||
}
|
||||
hash.update(buffer);
|
||||
return `sha256:${hash.digest("hex")}`;
|
||||
}
|
||||
const semantic = (value: unknown): unknown => {
|
||||
if (Array.isArray(value)) return value.map(semantic);
|
||||
if (value && typeof value === "object")
|
||||
|
||||
Reference in New Issue
Block a user