From 478a8a28af4b2b298745accffdc742f0ad6ce367 Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Thu, 11 Aug 2022 21:50:20 +0200 Subject: [PATCH] [wasm] fix error cases in assets loading (#73702) * fix bad rename of maxParallelDownloads - throttling * fix re-try logic * fixed counting of loaded assets * catching more error cases * fix blazor detection --- src/mono/wasm/runtime/assets.ts | 333 ++++++++++++++++++++---------- src/mono/wasm/runtime/dotnet.d.ts | 32 ++- src/mono/wasm/runtime/imports.ts | 9 +- src/mono/wasm/runtime/startup.ts | 9 +- src/mono/wasm/runtime/types.ts | 48 +++-- 5 files changed, 293 insertions(+), 138 deletions(-) diff --git a/src/mono/wasm/runtime/assets.ts b/src/mono/wasm/runtime/assets.ts index 362d6ba0f38a5..4830f0234093e 100644 --- a/src/mono/wasm/runtime/assets.ts +++ b/src/mono/wasm/runtime/assets.ts @@ -1,30 +1,50 @@ import cwraps from "./cwraps"; import { mono_wasm_load_icu_data } from "./icu"; -import { ENVIRONMENT_IS_WEB, Module, runtimeHelpers } from "./imports"; +import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_SHELL, ENVIRONMENT_IS_WEB, Module, runtimeHelpers } from "./imports"; import { mono_wasm_load_bytes_into_heap } from "./memory"; import { MONO } from "./net6-legacy/imports"; import { createPromiseController, PromiseAndController } from "./promise-controller"; import { delay } from "./promise-utils"; -import { beforeOnRuntimeInitialized } from "./startup"; -import { AssetBehaviours, AssetEntry, LoadingResource, mono_assert, ResourceRequest } from "./types"; +import { abort_startup, beforeOnRuntimeInitialized } from "./startup"; +import { AssetBehaviours, AssetEntry, AssetEntryInternal, LoadingResource, mono_assert, ResourceRequest } from "./types"; import { InstantiateWasmSuccessCallback, VoidPtr } from "./types/emscripten"; const allAssetsInMemory = createPromiseController(); const allDownloadsQueued = createPromiseController(); -let downloded_assets_count = 0; -let instantiated_assets_count = 0; +let actual_downloaded_assets_count = 0; +let actual_instantiated_assets_count = 0; +let expected_downloaded_assets_count = 0; +let expected_instantiated_assets_count = 0; const loaded_files: { url: string, file: string }[] = []; const loaded_assets: { [id: string]: [VoidPtr, number] } = Object.create(null); // in order to prevent net::ERR_INSUFFICIENT_RESOURCES if we start downloading too many files at same time let parallel_count = 0; -let throttling: PromiseAndController | undefined; -const skipDownloadsAssetTypes: { +let throttlingPromise: PromiseAndController | undefined; + +// don't `fetch` javaScript files +const skipDownloadsByAssetTypes: { [k: string]: boolean } = { "js-module-crypto": true, "js-module-threads": true, }; +// `response.arrayBuffer()` can't be called twice. Some usecases are calling it on response in the instantiation. +const skipBufferByAssetTypes: { + [k: string]: boolean +} = { + "dotnetwasm": true, +}; + +// these assets are instantiated differently than the main flow +const skipInstantiateByAssetTypes: { + [k: string]: boolean +} = { + "js-module-crypto": true, + "js-module-threads": true, + "dotnetwasm": true, +}; + export function resolve_asset_path(behavior: AssetBehaviours) { const asset: AssetEntry | undefined = runtimeHelpers.config.assets?.find(a => a.behavior == behavior); mono_assert(asset, () => `Can't find asset for ${behavior}`); @@ -33,39 +53,87 @@ export function resolve_asset_path(behavior: AssetBehaviours) { } return asset; } - +type AssetWithBuffer = { + asset: AssetEntryInternal, + buffer?: ArrayBuffer +} export async function mono_download_assets(): Promise { if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: mono_download_assets"); runtimeHelpers.maxParallelDownloads = runtimeHelpers.config.maxParallelDownloads || runtimeHelpers.maxParallelDownloads; try { - const download_promises: Promise[] = []; + const promises_of_assets_with_buffer: Promise[] = []; // start fetching and instantiating all assets in parallel - for (const asset of runtimeHelpers.config.assets!) { - if (!asset.pending && !skipDownloadsAssetTypes[asset.behavior]) { - download_promises.push(start_asset_download(asset)); + for (const a of runtimeHelpers.config.assets!) { + const asset: AssetEntryInternal = a; + if (!skipInstantiateByAssetTypes[asset.behavior]) { + expected_instantiated_assets_count++; + } + if (!skipDownloadsByAssetTypes[asset.behavior]) { + const headersOnly = skipBufferByAssetTypes[asset.behavior];// `response.arrayBuffer()` can't be called twice. Some usecases are calling it on response in the instantiation. + expected_downloaded_assets_count++; + if (asset.pendingDownload) { + asset.pendingDownloadInternal = asset.pendingDownload; + const waitForExternalData: () => Promise = async () => { + const response = await asset.pendingDownloadInternal!.response; + ++actual_downloaded_assets_count; + if (!headersOnly) { + asset.buffer = await response.arrayBuffer(); + } + return { asset, buffer: asset.buffer }; + }; + promises_of_assets_with_buffer.push(waitForExternalData()); + } else { + const waitForExternalData: () => Promise = async () => { + asset.buffer = await start_asset_download_with_retries(asset, !headersOnly); + return { asset, buffer: asset.buffer }; + }; + promises_of_assets_with_buffer.push(waitForExternalData()); + } } } allDownloadsQueued.promise_control.resolve(); - const asset_promises: Promise[] = []; - for (const downloadPromise of download_promises) { - const downloadedAsset = await downloadPromise; - if (downloadedAsset) { - asset_promises.push((async () => { - const url = downloadedAsset.pending!.url; - const response = await downloadedAsset.pending!.response; - downloadedAsset.pending = undefined; //GC - const buffer = await response.arrayBuffer(); - await beforeOnRuntimeInitialized.promise; - // this is after onRuntimeInitialized - _instantiate_asset(downloadedAsset, url, new Uint8Array(buffer)); - })()); - } + const promises_of_asset_instantiation: Promise[] = []; + for (const downloadPromise of promises_of_assets_with_buffer) { + promises_of_asset_instantiation.push((async () => { + const assetWithBuffer = await downloadPromise; + const asset = assetWithBuffer.asset; + if (assetWithBuffer.buffer) { + if (!skipInstantiateByAssetTypes[asset.behavior]) { + const url = asset.pendingDownloadInternal!.url; + const data = new Uint8Array(asset.buffer!); + asset.pendingDownloadInternal = null as any; // GC + asset.pendingDownload = null as any; // GC + asset.buffer = null as any; // GC + assetWithBuffer.buffer = null as any; // GC + + await beforeOnRuntimeInitialized.promise; + // this is after onRuntimeInitialized + _instantiate_asset(asset, url, data); + } + } else { + const headersOnly = skipBufferByAssetTypes[asset.behavior]; + if (!headersOnly) { + mono_assert(asset.isOptional, "Expected asset to have the downloaded buffer"); + if (!skipDownloadsByAssetTypes[asset.behavior]) { + expected_downloaded_assets_count--; + } + if (!skipInstantiateByAssetTypes[asset.behavior]) { + expected_instantiated_assets_count--; + } + } + } + })()); } // this await will get past the onRuntimeInitialized because we are not blocking via addRunDependency // and we are not awating it here - Promise.all(asset_promises).then(() => allAssetsInMemory.promise_control.resolve()); + Promise.all(promises_of_asset_instantiation).then(() => { + allAssetsInMemory.promise_control.resolve(); + }).catch(err => { + Module.printErr("MONO_WASM: Error in mono_download_assets: " + err); + abort_startup(err, true); + }); // OPTIMIZATION explained: // we do it this way so that we could allocate memory immediately after asset is downloaded (and after onRuntimeInitialized which happened already) // spreading in time @@ -76,72 +144,78 @@ export async function mono_download_assets(): Promise { } } -export async function start_asset_download(asset: AssetEntry): Promise { +// FIXME: Connection reset is probably the only good one for which we should retry +export async function start_asset_download_with_retries(asset: AssetEntryInternal, downloadData: boolean): Promise { try { - return await start_asset_download_throttle(asset); + return await start_asset_download_with_throttle(asset, downloadData); } catch (err: any) { + if (ENVIRONMENT_IS_SHELL || ENVIRONMENT_IS_NODE) { + // we will not re-try on shell + throw err; + } + if (asset.pendingDownload && asset.pendingDownloadInternal == asset.pendingDownload) { + // we will not re-try with external source + throw err; + } + if (asset.resolvedUrl && asset.resolvedUrl.indexOf("file://") != -1) { + // we will not re-try with local file + throw err; + } if (err && err.status == 404) { + // we will not re-try with 404 throw err; } + asset.pendingDownloadInternal = undefined; // second attempt only after all first attempts are queued await allDownloadsQueued.promise; try { - return await start_asset_download_throttle(asset); + return await start_asset_download_with_throttle(asset, downloadData); } catch (err) { + asset.pendingDownloadInternal = undefined; // third attempt after small delay await delay(100); - return await start_asset_download_throttle(asset); + return await start_asset_download_with_throttle(asset, downloadData); } } } -function resolve_path(asset: AssetEntry, sourcePrefix: string): string { - let attemptUrl; - const assemblyRootFolder = runtimeHelpers.config.assemblyRootFolder; - if (!asset.resolvedUrl) { - if (sourcePrefix === "") { - if (asset.behavior === "assembly" || asset.behavior === "pdb") - attemptUrl = assemblyRootFolder + "/" + asset.name; - else if (asset.behavior === "resource") { - const path = asset.culture !== "" ? `${asset.culture}/${asset.name}` : asset.name; - attemptUrl = assemblyRootFolder + "/" + path; - } - else { - attemptUrl = asset.name; - } - } else { - attemptUrl = sourcePrefix + asset.name; - } - attemptUrl = runtimeHelpers.locateFile(attemptUrl); - } - else { - attemptUrl = asset.resolvedUrl; +async function start_asset_download_with_throttle(asset: AssetEntry, downloadData: boolean): Promise { + // we don't addRunDependency to allow download in parallel with onRuntimeInitialized event! + while (throttlingPromise) { + await throttlingPromise.promise; } - return attemptUrl; -} + try { + ++parallel_count; + if (parallel_count == runtimeHelpers.maxParallelDownloads) { + if (runtimeHelpers.diagnosticTracing) + console.debug("MONO_WASM: Throttling further parallel downloads"); + throttlingPromise = createPromiseController(); + } -function download_resource(request: ResourceRequest): LoadingResource { - if (typeof Module.downloadResource === "function") { - const loading = Module.downloadResource(request); - if (loading) return loading; + const response = await start_asset_download_sources(asset); + if (!downloadData || !response) { + return undefined; + } + return await response.arrayBuffer(); } - const options: any = {}; - if (request.hash) { - options.integrity = request.hash; + finally { + --parallel_count; + if (throttlingPromise && parallel_count == runtimeHelpers.maxParallelDownloads - 1) { + if (runtimeHelpers.diagnosticTracing) + console.debug("MONO_WASM: Resuming more parallel downloads"); + const old_throttling = throttlingPromise; + throttlingPromise = undefined; + old_throttling.promise_control.resolve(); + } } - const response = runtimeHelpers.fetch_like(request.resolvedUrl!, options); - return { - name: request.name, url: request.resolvedUrl!, response - }; } -async function start_asset_download_sources(asset: AssetEntry): Promise { +async function start_asset_download_sources(asset: AssetEntryInternal): Promise { // we don't addRunDependency to allow download in parallel with onRuntimeInitialized event! if (asset.buffer) { - ++downloded_assets_count; const buffer = asset.buffer; - asset.buffer = undefined;//GC later - asset.pending = { + asset.buffer = null as any; // GC + asset.pendingDownloadInternal = { url: "undefined://" + asset.name, name: asset.name, response: Promise.resolve({ @@ -151,11 +225,12 @@ async function start_asset_download_sources(asset: AssetEntry): Promise `Response undefined ${asset.name}`); + if (!isOkToFail) { + const err: any = new Error(`MONO_WASM: download '${response.url}' for ${asset.name} failed ${response.status} ${response.statusText}`); + err.status = response.status; + throw err; + } else { + Module.print(`MONO_WASM: optional download '${response.url}' for ${asset.name} failed ${response.status} ${response.statusText}`); + return undefined; + } } -async function start_asset_download_throttle(asset: AssetEntry): Promise { - // we don't addRunDependency to allow download in parallel with onRuntimeInitialized event! - while (throttling) { - await throttling.promise; - } - try { - ++parallel_count; - if (parallel_count == runtimeHelpers.maxParallelDownloads) { - if (runtimeHelpers.diagnosticTracing) - console.debug("MONO_WASM: Throttling further parallel downloads"); - throttling = createPromiseController(); +function resolve_path(asset: AssetEntry, sourcePrefix: string): string { + mono_assert(sourcePrefix !== null && sourcePrefix !== undefined, () => `sourcePrefix must be provided for ${asset.name}`); + let attemptUrl; + const assemblyRootFolder = runtimeHelpers.config.assemblyRootFolder; + if (!asset.resolvedUrl) { + if (sourcePrefix === "") { + if (asset.behavior === "assembly" || asset.behavior === "pdb") { + attemptUrl = assemblyRootFolder + ? (assemblyRootFolder + "/" + asset.name) + : asset.name; + } + else if (asset.behavior === "resource") { + const path = asset.culture !== "" ? `${asset.culture}/${asset.name}` : asset.name; + attemptUrl = assemblyRootFolder + ? (assemblyRootFolder + "/" + path) + : path; + } + else { + attemptUrl = asset.name; + } + } else { + attemptUrl = sourcePrefix + asset.name; } - return await start_asset_download_sources(asset); + attemptUrl = runtimeHelpers.locateFile(attemptUrl); } - catch (response: any) { - const isOkToFail = asset.isOptional || (asset.name.match(/\.pdb$/) && runtimeHelpers.config.ignorePdbLoadErrors); - if (!isOkToFail) { - const err: any = new Error(`MONO_WASM: download '${response.url}' for ${asset.name} failed ${response.status} ${response.statusText}`); - err.status = response.status; - throw err; - } + else { + attemptUrl = asset.resolvedUrl; } - finally { - --parallel_count; - if (throttling && parallel_count == runtimeHelpers.maxParallelDownloads - 1) { - if (runtimeHelpers.diagnosticTracing) - console.debug("MONO_WASM: Resuming more parallel downloads"); - const old_throttling = throttling; - throttling = undefined; - old_throttling.promise_control.resolve(); + mono_assert(attemptUrl && typeof attemptUrl == "string", "attemptUrl need to be path or url string"); + return attemptUrl; +} + +function download_resource(request: ResourceRequest): LoadingResource { + try { + if (typeof Module.downloadResource === "function") { + const loading = Module.downloadResource(request); + if (loading) return loading; + } + const options: any = {}; + if (request.hash) { + options.integrity = request.hash; } + const response = runtimeHelpers.fetch_like(request.resolvedUrl!, options); + return { + name: request.name, url: request.resolvedUrl!, response + }; + } catch (err) { + const response = { + ok: false, + url: request.resolvedUrl, + status: 500, + statusText: "ERR29: " + err, + arrayBuffer: () => { throw err; }, + json: () => { throw err; } + }; + return { + name: request.name, url: request.resolvedUrl!, response: Promise.resolve(response) + }; } } @@ -311,16 +422,16 @@ function _instantiate_asset(asset: AssetEntry, url: string, bytes: Uint8Array) { else if (asset.behavior === "resource") { cwraps.mono_wasm_add_satellite_assembly(virtualName, asset.culture!, offset!, bytes.length); } - ++instantiated_assets_count; + ++actual_instantiated_assets_count; } export async function instantiate_wasm_asset( - pendingAsset: AssetEntry, + pendingAsset: AssetEntryInternal, wasmModuleImports: WebAssembly.Imports, successCallback: InstantiateWasmSuccessCallback, ) { - mono_assert(pendingAsset && pendingAsset.pending, "Can't load dotnet.wasm"); - const response = await pendingAsset.pending.response; + mono_assert(pendingAsset && pendingAsset.pendingDownloadInternal, "Can't load dotnet.wasm"); + const response = await pendingAsset.pendingDownloadInternal.response; const contentType = response.headers ? response.headers.get("Content-Type") : undefined; let compiledInstance: WebAssembly.Instance; let compiledModule: WebAssembly.Module; @@ -339,7 +450,6 @@ export async function instantiate_wasm_asset( compiledInstance = arrayBufferResult.instance; compiledModule = arrayBufferResult.module; } - ++instantiated_assets_count; successCallback(compiledInstance, compiledModule); } @@ -401,9 +511,8 @@ export async function wait_for_all_assets() { // wait for all assets in memory await allAssetsInMemory.promise; if (runtimeHelpers.config.assets) { - const expected_asset_count = runtimeHelpers.config.assets.filter(a => !skipDownloadsAssetTypes[a.behavior]).length; - mono_assert(downloded_assets_count == expected_asset_count, "Expected assets to be downloaded"); - mono_assert(instantiated_assets_count == expected_asset_count, "Expected assets to be in memory"); + mono_assert(actual_downloaded_assets_count == expected_downloaded_assets_count, () => `Expected ${expected_downloaded_assets_count} assets to be downloaded, but only finished ${actual_downloaded_assets_count}`); + mono_assert(actual_instantiated_assets_count == expected_instantiated_assets_count, () => `Expected ${expected_instantiated_assets_count} assets to be in memory, but only instantiated ${actual_instantiated_assets_count}`); loaded_files.forEach(value => MONO.loaded_files.push(value.url)); if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: all assets are loaded in wasm memory"); } diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index eacddc7a017bb..4d2208681b471 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -88,13 +88,38 @@ interface ResourceRequest { resolvedUrl?: string; hash?: string; } +interface LoadingResource { + name: string; + url: string; + response: Promise; +} interface AssetEntry extends ResourceRequest { + /** + * If specified, overrides the path of the asset in the virtual filesystem and similar data structures once downloaded. + */ virtualPath?: string; + /** + * Culture code + */ culture?: string; + /** + * If true, an attempt will be made to load the asset from each location in MonoConfig.remoteSources. + */ loadRemote?: boolean; + /** + * If true, the runtime startup would not fail if the asset download was not successful. + */ isOptional?: boolean; + /** + * If provided, runtime doesn't have to fetch the data. + * Runtime would set the buffer to null after instantiation to free the memory. + */ buffer?: ArrayBuffer; - pending?: LoadingResource; + /** + * It's metadata + fetch-like Promise + * If provided, the runtime doesn't have to initiate the download. It would just await the response. + */ + pendingDownload?: LoadingResource; } declare type AssetBehaviours = "resource" | "assembly" | "pdb" | "heap" | "icu" | "vfs" | "dotnetwasm" | "js-module-crypto" | "js-module-threads"; declare type GlobalizationMode = "icu" | // load ICU globalization data from any runtime assets with behavior "icu". @@ -118,11 +143,6 @@ declare type DotnetModuleConfig = { exports?: string[]; downloadResource?: (request: ResourceRequest) => LoadingResource | undefined; } & Partial; -interface LoadingResource { - name: string; - url: string; - response: Promise; -} declare type APIType = { runMain: (mainAssemblyName: string, args: string[]) => Promise; diff --git a/src/mono/wasm/runtime/imports.ts b/src/mono/wasm/runtime/imports.ts index 2e4a619238fd7..48142cccd4faa 100644 --- a/src/mono/wasm/runtime/imports.ts +++ b/src/mono/wasm/runtime/imports.ts @@ -39,12 +39,13 @@ export function set_imports_exports( runtimeHelpers.requirePromise = imports.requirePromise; } -export const runtimeHelpers: RuntimeHelpers = { - javaScriptExports: {}, +const initialRuntimeHelpers: Partial = +{ + javaScriptExports: {} as any, mono_wasm_load_runtime_done: false, mono_wasm_bindings_is_ready: false, - max_parallel_downloads: 16, + maxParallelDownloads: 16, config: {}, diagnosticTracing: false, - fetch: null }; +export const runtimeHelpers: RuntimeHelpers = initialRuntimeHelpers as any; diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index ff9f53f74a41a..9ee66858a7ea7 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -24,7 +24,7 @@ import { cwraps_internal } from "./exports-internal"; import { cwraps_binding_api, cwraps_mono_api } from "./net6-legacy/exports-legacy"; import { DotnetPublicAPI } from "./exports"; import { CharPtr, InstantiateWasmCallBack, InstantiateWasmSuccessCallback } from "./types/emscripten"; -import { instantiate_wasm_asset, mono_download_assets, resolve_asset_path, start_asset_download, wait_for_all_assets } from "./assets"; +import { instantiate_wasm_asset, mono_download_assets, resolve_asset_path, start_asset_download_with_retries, wait_for_all_assets } from "./assets"; import { BINDING, MONO } from "./net6-legacy/imports"; import { readSymbolMapFile } from "./logging"; import { mono_wasm_init_diagnostics } from "./diagnostics"; @@ -53,7 +53,7 @@ export function configure_emscripten_startup(module: DotnetModule, exportedAPI: // eslint-disable-next-line @typescript-eslint/no-empty-function const userOnRuntimeInitialized: () => void = module.onRuntimeInitialized ? module.onRuntimeInitialized : () => { }; // when assets don't contain DLLs it means this is Blazor or another custom startup - isCustomStartup = !module.configSrc && (!module.config || !module.config.assets || module.config.assets.findIndex(a => a.behavior === "assembly") != -1); // like blazor + isCustomStartup = !module.configSrc && (!module.config || !module.config.assets || module.config.assets.findIndex(a => a.behavior === "assembly") == -1); // like blazor // execution order == [0] == // - default or user Module.instantiateWasm (will start downloading dotnet.wasm) @@ -366,10 +366,11 @@ async function instantiate_wasm_module( await mono_wasm_load_config(Module.configSrc); if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: instantiate_wasm_module"); const assetToLoad = resolve_asset_path("dotnetwasm"); - const pendingAsset = await start_asset_download(assetToLoad); + // FIXME: this would not apply re-try (on connection reset during download) for dotnet.wasm because we could not download the buffer before we pass it to instantiate_wasm_asset + await start_asset_download_with_retries(assetToLoad, false); await beforePreInit.promise; Module.addRunDependency("instantiate_wasm_module"); - instantiate_wasm_asset(pendingAsset!, imports, successCallback); + instantiate_wasm_asset(assetToLoad, imports, successCallback); if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: instantiate_wasm_module done"); afterInstantiateWasm.promise_control.resolve(); diff --git a/src/mono/wasm/runtime/types.ts b/src/mono/wasm/runtime/types.ts index d9edc82f9587b..b7082b21f4150 100644 --- a/src/mono/wasm/runtime/types.ts +++ b/src/mono/wasm/runtime/types.ts @@ -99,14 +99,45 @@ export interface ResourceRequest { hash?: string; } +export interface LoadingResource { + name: string; + url: string; + response: Promise; +} + // Types of assets that can be in the mono-config.js/mono-config.json file (taken from /src/tasks/WasmAppBuilder/WasmAppBuilder.cs) export interface AssetEntry extends ResourceRequest { - virtualPath?: string, // if specified, overrides the path of the asset in the virtual filesystem and similar data structures once loaded. + /** + * If specified, overrides the path of the asset in the virtual filesystem and similar data structures once downloaded. + */ + virtualPath?: string, + /** + * Culture code + */ culture?: string, - loadRemote?: boolean, // if true, an attempt will be made to load the asset from each location in @args.remoteSources. - isOptional?: boolean // if true, any failure to load this asset will be ignored. - buffer?: ArrayBuffer // if provided, we don't have to fetch it - pending?: LoadingResource // if provided, we don't have to start fetching it + /** + * If true, an attempt will be made to load the asset from each location in MonoConfig.remoteSources. + */ + loadRemote?: boolean, // + /** + * If true, the runtime startup would not fail if the asset download was not successful. + */ + isOptional?: boolean + /** + * If provided, runtime doesn't have to fetch the data. + * Runtime would set the buffer to null after instantiation to free the memory. + */ + buffer?: ArrayBuffer + /** + * It's metadata + fetch-like Promise + * If provided, the runtime doesn't have to initiate the download. It would just await the response. + */ + pendingDownload?: LoadingResource +} + +export interface AssetEntryInternal extends AssetEntry { + // this is almost the same as pendingDownload, but it could have multiple values in time, because of re-try download logic + pendingDownloadInternal?: LoadingResource } export type AssetBehaviours = @@ -198,13 +229,6 @@ export type DotnetModuleConfigImports = { url?: any; } -export interface LoadingResource { - name: string; - url: string; - response: Promise; -} - - // see src\mono\wasm\runtime\rollup.config.js // inline this, because the lambda could allocate closure on hot path otherwise export function mono_assert(condition: unknown, messageFactory: string | (() => string)): asserts condition {