[browser] improve asset loading (#81886)
authorPavel Savara <pavel.savara@gmail.com>
Thu, 9 Feb 2023 18:39:48 +0000 (19:39 +0100)
committerGitHub <noreply@github.com>
Thu, 9 Feb 2023 18:39:48 +0000 (19:39 +0100)
* new `enableDownloadRetry` configuration
* fix for actual_downloaded_assets_count assert in use case with `asset.pendingDownload`
* make `AssetEntry` mutable and pass it back to `downloadResource` API callback

Co-authored-by: Marek FiĊĦera <mara@neptuo.com>
src/mono/wasm/runtime/assets.ts
src/mono/wasm/runtime/dotnet.d.ts
src/mono/wasm/runtime/imports.ts
src/mono/wasm/runtime/startup.ts
src/mono/wasm/runtime/types.ts

index 5711b79..e9b98a8 100644 (file)
@@ -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<void> {
     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<AssetWithBuffer>[] = [];
+        const promises_of_assets: Promise<AssetEntryInternal>[] = [];
         // 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<void> {
             }
             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<void>[] = [];
-        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<void> {
                         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<void> {
                         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<void> {
     }
 }
 
-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<ArrayBuffer | undefined> {
+export async function start_asset_download(asset: AssetEntryInternal): Promise<AssetEntryInternal> {
     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<ArrayBuffer | undefined> {
+async function start_asset_download_with_throttle(asset: AssetEntryInternal): Promise<AssetEntryInternal> {
     // 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<Response | undefined> {
     // 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) {
index f8dd4b8..bcfce61 100644 (file)
@@ -101,6 +101,10 @@ type MonoConfig = {
      */
     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
      */
     mainAssemblyName?: string;
index 01a3f9c..1a7af3d 100644 (file)
@@ -54,6 +54,7 @@ const initialRuntimeHelpers: Partial<RuntimeHelpers> =
     mono_wasm_load_runtime_done: false,
     mono_wasm_bindings_is_ready: false,
     maxParallelDownloads: 16,
+    enableDownloadRetry: true,
     config: {
         environmentVariables: {},
     },
index 25bf207..3fea127 100644 (file)
@@ -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();
index e55f1ed..09b45d9 100644 (file)
@@ -88,6 +88,10 @@ export type MonoConfig = {
      */
     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
      */
     mainAssemblyName?: string,
@@ -214,6 +218,7 @@ export type RuntimeHelpers = {
 
     loaded_files: string[];
     maxParallelDownloads: number;
+    enableDownloadRetry: boolean;
     config: MonoConfigInternal;
     diagnosticTracing: boolean;
     enablePerfMeasure: boolean;