Skip to content

[browser] improve asset loading #81886

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 37 additions & 49 deletions src/mono/wasm/runtime/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -78,25 +75,23 @@ 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");
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
Expand All @@ -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;
}
}
}
})());
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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
Expand All @@ -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;
Expand All @@ -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) {
Expand Down
4 changes: 4 additions & 0 deletions src/mono/wasm/runtime/dotnet.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
1 change: 1 addition & 0 deletions src/mono/wasm/runtime/imports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {},
},
Expand Down
3 changes: 3 additions & 0 deletions src/mono/wasm/runtime/startup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
5 changes: 5 additions & 0 deletions src/mono/wasm/runtime/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -214,6 +218,7 @@ export type RuntimeHelpers = {

loaded_files: string[];
maxParallelDownloads: number;
enableDownloadRetry: boolean;
config: MonoConfigInternal;
diagnosticTracing: boolean;
enablePerfMeasure: boolean;
Expand Down