Advance M7 workflows and release operations
This commit is contained in:
195
web/app/src/engine-client/engine-variant-bootstrap.ts
Normal file
195
web/app/src/engine-client/engine-variant-bootstrap.ts
Normal file
@@ -0,0 +1,195 @@
|
||||
import {
|
||||
bindWebEngineRelease,
|
||||
selectWebEngineVariant,
|
||||
type WasmThreadingCapabilities,
|
||||
type WebEngineReleaseBinding,
|
||||
type WebEngineVariantPolicy,
|
||||
} from "../../../protocol/engine-variant";
|
||||
import type { CapabilityGateResult } from "../../../protocol/capability-gates";
|
||||
import type { WebEngineManifestV2, WebEngineVariantV2 } from "../../../protocol/manifest";
|
||||
|
||||
export interface EngineVariantResourceState {
|
||||
handles: number;
|
||||
workers: number;
|
||||
timers: number;
|
||||
pendingRequests: number;
|
||||
}
|
||||
|
||||
export interface EngineVariantSession<Project = ArrayBuffer> {
|
||||
readonly variant: WebEngineVariantV2;
|
||||
openProject(project: Project): Promise<void>;
|
||||
dispose(): Promise<EngineVariantResourceState>;
|
||||
resourceState(): EngineVariantResourceState;
|
||||
testOnlySeedTrackedResources?(): void;
|
||||
}
|
||||
|
||||
export interface EngineVariantFallbackReason {
|
||||
code: "PTHREAD_INITIALIZATION_FAILED";
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface EngineVariantBootstrapResult {
|
||||
status: "READY";
|
||||
selected: "single" | "pthread";
|
||||
attempted: Array<"single" | "pthread">;
|
||||
fallbackReason: EngineVariantFallbackReason | null;
|
||||
pthreadGate: CapabilityGateResult;
|
||||
openCount: number;
|
||||
failedAttemptCleanup: EngineVariantResourceState | null;
|
||||
}
|
||||
|
||||
export interface EngineVariantBootstrapOutcome<Project = ArrayBuffer> {
|
||||
result: EngineVariantBootstrapResult;
|
||||
session: EngineVariantSession<Project>;
|
||||
}
|
||||
|
||||
export type WebEngineRefreshRequired = Extract<WebEngineReleaseBinding, { status: "REFRESH_REQUIRED" }>;
|
||||
|
||||
export type WebEngineReleaseBootstrapOutcome<Project = ArrayBuffer> =
|
||||
| EngineVariantBootstrapOutcome<Project>
|
||||
| { result: WebEngineRefreshRequired; session: null };
|
||||
|
||||
export interface EngineVariantBootstrapDependencies<Project = ArrayBuffer> {
|
||||
initialize(variant: WebEngineVariantV2): Promise<EngineVariantSession<Project>>;
|
||||
}
|
||||
|
||||
export interface EngineVariantBootstrapTestHooks {
|
||||
failPthreadAfterInitialize?: boolean;
|
||||
seedTrackedPthreadResources?: boolean;
|
||||
}
|
||||
|
||||
export class EngineVariantLoadError extends Error {
|
||||
readonly code: "ENGINE_VARIANT_RESOURCE_REQUEST_FAILED" | "ENGINE_VARIANT_RESOURCE_HASH_MISMATCH";
|
||||
|
||||
constructor(code: EngineVariantLoadError["code"], message: string, options?: ErrorOptions) {
|
||||
super(message, options);
|
||||
this.name = "EngineVariantLoadError";
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
|
||||
export class EngineVariantBootstrapError extends Error {
|
||||
readonly code: "ENGINE_VARIANT_INTEGRITY_FAILED";
|
||||
readonly attempted: Array<"single" | "pthread">;
|
||||
|
||||
constructor(attempted: Array<"single" | "pthread">, cause: EngineVariantLoadError) {
|
||||
super(`${cause.code}: ${cause.message}`, { cause });
|
||||
this.name = "EngineVariantBootstrapError";
|
||||
this.code = "ENGINE_VARIANT_INTEGRITY_FAILED";
|
||||
this.attempted = [...attempted];
|
||||
}
|
||||
}
|
||||
|
||||
function failureMessage(error: unknown): string {
|
||||
return error instanceof Error && error.message ? error.message : "pthread engine initialization failed";
|
||||
}
|
||||
|
||||
function rejectIntegrityFailure(
|
||||
error: unknown,
|
||||
attempted: Array<"single" | "pthread">,
|
||||
): void {
|
||||
if (error instanceof EngineVariantLoadError && error.code === "ENGINE_VARIANT_RESOURCE_HASH_MISMATCH") {
|
||||
throw new EngineVariantBootstrapError(attempted, error);
|
||||
}
|
||||
}
|
||||
|
||||
async function bootstrapSelectedWebEngineVariant<Project = ArrayBuffer>(
|
||||
manifest: WebEngineManifestV2,
|
||||
policy: WebEngineVariantPolicy,
|
||||
capabilities: WasmThreadingCapabilities,
|
||||
dependencies: EngineVariantBootstrapDependencies<Project>,
|
||||
pendingProject?: Project,
|
||||
testHooks?: EngineVariantBootstrapTestHooks,
|
||||
): Promise<EngineVariantBootstrapOutcome<Project>> {
|
||||
const selection = selectWebEngineVariant(manifest, policy, capabilities);
|
||||
if (!selection.selectedVariant) throw new Error("PTHREAD_REQUIRED_CAPABILITY_BLOCKED");
|
||||
|
||||
const attempted: Array<"single" | "pthread"> = [];
|
||||
let failedAttemptCleanup: EngineVariantResourceState | null = null;
|
||||
let fallbackReason: EngineVariantFallbackReason | null = null;
|
||||
let session: EngineVariantSession<Project> | null = null;
|
||||
|
||||
const initialize = async (variant: WebEngineVariantV2): Promise<EngineVariantSession<Project>> => {
|
||||
attempted.push(variant.id);
|
||||
return dependencies.initialize(variant);
|
||||
};
|
||||
|
||||
try {
|
||||
session = await initialize(selection.selectedVariant);
|
||||
if (selection.selectedVariant.id === "pthread") {
|
||||
if (testHooks?.seedTrackedPthreadResources) session.testOnlySeedTrackedResources?.();
|
||||
if (testHooks?.failPthreadAfterInitialize) throw new Error("TEST_PTHREAD_INITIALIZATION_FAILURE");
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
const failedSession = session;
|
||||
if (failedSession) failedAttemptCleanup = await failedSession.dispose();
|
||||
rejectIntegrityFailure(error, attempted);
|
||||
if (policy !== "AUTO" || selection.selectedVariant.id !== "pthread") throw error;
|
||||
fallbackReason = { code: "PTHREAD_INITIALIZATION_FAILED", message: failureMessage(error) };
|
||||
const single = manifest.variants.find((variant) => variant.id === "single");
|
||||
if (!single) throw new Error("ENGINE_MANIFEST_VARIANTS_MISSING", { cause: error });
|
||||
try {
|
||||
session = await initialize(single);
|
||||
}
|
||||
catch (fallbackError) {
|
||||
rejectIntegrityFailure(fallbackError, attempted);
|
||||
throw fallbackError;
|
||||
}
|
||||
}
|
||||
|
||||
if (!session) throw new Error("ENGINE_VARIANT_INITIALIZATION_FAILED");
|
||||
let openCount = 0;
|
||||
if (pendingProject !== undefined) {
|
||||
try {
|
||||
await session.openProject(pendingProject);
|
||||
openCount = 1;
|
||||
}
|
||||
catch (error) {
|
||||
await session.dispose();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
return {
|
||||
result: {
|
||||
status: "READY",
|
||||
selected: session.variant.id,
|
||||
attempted,
|
||||
fallbackReason,
|
||||
pthreadGate: selection.pthreadGate,
|
||||
openCount,
|
||||
failedAttemptCleanup,
|
||||
},
|
||||
session,
|
||||
};
|
||||
}
|
||||
|
||||
export async function bootstrapWebEngineRelease<Project = ArrayBuffer>(
|
||||
expectedReleaseId: string,
|
||||
manifest: WebEngineManifestV2,
|
||||
policy: WebEngineVariantPolicy,
|
||||
capabilities: WasmThreadingCapabilities,
|
||||
dependencies: EngineVariantBootstrapDependencies<Project>,
|
||||
pendingProject?: Project,
|
||||
testHooks?: EngineVariantBootstrapTestHooks,
|
||||
): Promise<WebEngineReleaseBootstrapOutcome<Project>> {
|
||||
const binding = bindWebEngineRelease(expectedReleaseId, manifest);
|
||||
if (binding.status === "REFRESH_REQUIRED") {
|
||||
return { result: binding, session: null };
|
||||
}
|
||||
return bootstrapSelectedWebEngineVariant(
|
||||
binding.manifest,
|
||||
policy,
|
||||
capabilities,
|
||||
dependencies,
|
||||
pendingProject,
|
||||
testHooks,
|
||||
);
|
||||
}
|
||||
|
||||
export function engineVariantStatusLabel(result: EngineVariantBootstrapResult): string {
|
||||
if (result.selected === "pthread") return "Runtime: pthread";
|
||||
return result.fallbackReason
|
||||
? `Runtime: single (pthread fallback: ${result.fallbackReason.code})`
|
||||
: "Runtime: single";
|
||||
}
|
||||
Reference in New Issue
Block a user