diff --git a/src/mono/wasm/runtime/assets.ts b/src/mono/wasm/runtime/assets.ts index 4cb2e763168e6d..54459a6ab3b3c2 100644 --- a/src/mono/wasm/runtime/assets.ts +++ b/src/mono/wasm/runtime/assets.ts @@ -48,22 +48,19 @@ const skipInstantiateByAssetTypes: { }; export function resolve_asset_path(behavior: AssetBehaviours) { - const asset: AssetEntry | undefined = runtimeHelpers.config.assets?.find(a => a.behavior == behavior); + const asset: AssetEntryInternal | undefined = runtimeHelpers.config.assets?.find(a => a.behavior == behavior); mono_assert(asset, () => `Can't find asset for ${behavior}`); if (!asset.resolvedUrl) { asset.resolvedUrl = resolve_path(asset, ""); } 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; + runtimeHelpers.enableDownloadRetry = runtimeHelpers.config.enableDownloadRetry || runtimeHelpers.enableDownloadRetry; try { - const promises_of_assets_with_buffer: Promise[] = []; + const promises_of_assets: Promise[] = []; // start fetching and instantiating all assets in parallel for (const a of runtimeHelpers.config.assets!) { const asset: AssetEntryInternal = a; @@ -78,17 +75,16 @@ export async function mono_download_assets(): Promise { } if (!skipDownloadsByAssetTypes[asset.behavior]) { expected_downloaded_assets_count++; - promises_of_assets_with_buffer.push(start_asset_download(asset)); + promises_of_assets.push(start_asset_download(asset)); } } allDownloadsQueued.promise_control.resolve(); const promises_of_asset_instantiation: Promise[] = []; - for (const downloadPromise of promises_of_assets_with_buffer) { + for (const downloadPromise of promises_of_assets) { promises_of_asset_instantiation.push((async () => { - const assetWithBuffer = await downloadPromise; - const asset = assetWithBuffer.asset; - if (assetWithBuffer.buffer) { + const asset = await downloadPromise; + if (asset.buffer) { if (!skipInstantiateByAssetTypes[asset.behavior]) { const url = asset.pendingDownloadInternal!.url; mono_assert(asset.buffer && typeof asset.buffer === "object", "asset buffer must be array or buffer like"); @@ -96,7 +92,6 @@ export async function mono_download_assets(): Promise { 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 @@ -112,6 +107,10 @@ export async function mono_download_assets(): Promise { if (!skipInstantiateByAssetTypes[asset.behavior]) { expected_instantiated_assets_count--; } + } else { + if (skipBufferByAssetTypes[asset.behavior]) { + ++actual_downloaded_assets_count; + } } } })()); @@ -135,28 +134,15 @@ export async function mono_download_assets(): Promise { } } -export async function start_asset_download(asset: AssetEntryInternal) { - // `response.arrayBuffer()` can't be called twice. Some use-cases are calling it on response in the instantiation. - const headersOnly = skipBufferByAssetTypes[asset.behavior]; - if (asset.pendingDownload) { - asset.pendingDownloadInternal = asset.pendingDownload; - const response = await asset.pendingDownloadInternal!.response; - ++actual_downloaded_assets_count; - if (!headersOnly) { - asset.buffer = await response.arrayBuffer(); - } - return { asset, buffer: asset.buffer }; - } else { - asset.buffer = await start_asset_download_with_retries(asset, !headersOnly); - return { asset, buffer: asset.buffer }; - } -} - // FIXME: Connection reset is probably the only good one for which we should retry -async function start_asset_download_with_retries(asset: AssetEntryInternal, downloadData: boolean): Promise { +export async function start_asset_download(asset: AssetEntryInternal): Promise { try { - return await start_asset_download_with_throttle(asset, downloadData); + return await start_asset_download_with_throttle(asset); } catch (err: any) { + if (!runtimeHelpers.enableDownloadRetry) { + // we will not re-try if disabled + throw err; + } if (ENVIRONMENT_IS_SHELL || ENVIRONMENT_IS_NODE) { // we will not re-try on shell throw err; @@ -177,17 +163,17 @@ async function start_asset_download_with_retries(asset: AssetEntryInternal, down // second attempt only after all first attempts are queued await allDownloadsQueued.promise; try { - return await start_asset_download_with_throttle(asset, downloadData); + return await start_asset_download_with_throttle(asset); } catch (err) { asset.pendingDownloadInternal = undefined; // third attempt after small delay await delay(100); - return await start_asset_download_with_throttle(asset, downloadData); + return await start_asset_download_with_throttle(asset); } } } -async function start_asset_download_with_throttle(asset: AssetEntry, downloadData: boolean): Promise { +async function start_asset_download_with_throttle(asset: AssetEntryInternal): Promise { // we don't addRunDependency to allow download in parallel with onRuntimeInitialized event! while (throttlingPromise) { await throttlingPromise.promise; @@ -201,10 +187,16 @@ async function start_asset_download_with_throttle(asset: AssetEntry, downloadDat } const response = await start_asset_download_sources(asset); - if (!downloadData || !response) { - return undefined; + if (!response) { + return asset; } - return await response.arrayBuffer(); + const skipBuffer = skipBufferByAssetTypes[asset.behavior]; + if (skipBuffer) { + return asset; + } + asset.buffer = await response.arrayBuffer(); + ++actual_downloaded_assets_count; + return asset; } finally { --parallel_count; @@ -220,6 +212,12 @@ async function start_asset_download_with_throttle(asset: AssetEntry, downloadDat async function start_asset_download_sources(asset: AssetEntryInternal): Promise { // we don't addRunDependency to allow download in parallel with onRuntimeInitialized event! + if (asset.pendingDownload) { + asset.pendingDownloadInternal = asset.pendingDownload; + } + if (asset.pendingDownloadInternal && asset.pendingDownloadInternal.response) { + return asset.pendingDownloadInternal.response; + } if (asset.buffer) { const buffer = asset.buffer; asset.buffer = null as any; // GC @@ -233,13 +231,8 @@ async function start_asset_download_sources(asset: AssetEntryInternal): Promise< } }) as any }; - ++actual_downloaded_assets_count; return asset.pendingDownloadInternal.response; } - if (asset.pendingDownloadInternal && asset.pendingDownloadInternal.response) { - const response = await asset.pendingDownloadInternal.response; - return response; - } const sourcesList = asset.loadRemote && runtimeHelpers.config.remoteSources ? runtimeHelpers.config.remoteSources : [""]; let response: Response | undefined = undefined; @@ -258,18 +251,13 @@ async function start_asset_download_sources(asset: AssetEntryInternal): Promise< console.debug(`MONO_WASM: Attempting to download '${attemptUrl}' for ${asset.name}`); } try { - const loadingResource = download_resource({ - name: asset.name, - resolvedUrl: attemptUrl, - hash: asset.hash, - behavior: asset.behavior - }); + asset.resolvedUrl = attemptUrl; + const loadingResource = download_resource(asset); asset.pendingDownloadInternal = loadingResource; response = await loadingResource.response; if (!response.ok) { continue;// next source } - ++actual_downloaded_assets_count; return response; } catch (err) { diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index f8dd4b81c884b8..bcfce611388fee 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -100,6 +100,10 @@ type MonoConfig = { * We are throttling parallel downloads in order to avoid net::ERR_INSUFFICIENT_RESOURCES on chrome. The default value is 16. */ maxParallelDownloads?: number; + /** + * We are making up to 2 more delayed attempts to download same asset. Default true. + */ + enableDownloadRetry?: boolean; /** * Name of the assembly with main entrypoint */ diff --git a/src/mono/wasm/runtime/imports.ts b/src/mono/wasm/runtime/imports.ts index 01a3f9cdcf4e45..1a7af3d7a7c9b7 100644 --- a/src/mono/wasm/runtime/imports.ts +++ b/src/mono/wasm/runtime/imports.ts @@ -54,6 +54,7 @@ const initialRuntimeHelpers: Partial = mono_wasm_load_runtime_done: false, mono_wasm_bindings_is_ready: false, maxParallelDownloads: 16, + enableDownloadRetry: true, config: { environmentVariables: {}, }, diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index 25bf207d30eb0b..3fea12709290ab 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -470,6 +470,9 @@ async function instantiate_wasm_module( await beforePreInit.promise; Module.addRunDependency("instantiate_wasm_module"); await instantiate_wasm_asset(assetToLoad, imports, successCallback); + assetToLoad.pendingDownloadInternal = null as any; // GC + assetToLoad.pendingDownload = null as any; // GC + assetToLoad.buffer = null as any; // GC 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 e55f1ed60db46f..09b45d96ff10d9 100644 --- a/src/mono/wasm/runtime/types.ts +++ b/src/mono/wasm/runtime/types.ts @@ -87,6 +87,10 @@ export type MonoConfig = { * We are throttling parallel downloads in order to avoid net::ERR_INSUFFICIENT_RESOURCES on chrome. The default value is 16. */ maxParallelDownloads?: number, + /** + * We are making up to 2 more delayed attempts to download same asset. Default true. + */ + enableDownloadRetry?: boolean, /** * Name of the assembly with main entrypoint */ @@ -214,6 +218,7 @@ export type RuntimeHelpers = { loaded_files: string[]; maxParallelDownloads: number; + enableDownloadRetry: boolean; config: MonoConfigInternal; diagnosticTracing: boolean; enablePerfMeasure: boolean;