Skip to content

Commit 5d3e70a

Browse files
authored
[wasm] parallel asset loading (#61610)
1 parent fdafc7c commit 5d3e70a

File tree

3 files changed

+82
-125
lines changed

3 files changed

+82
-125
lines changed

src/mono/sample/wasm/Directory.Build.targets

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,6 @@
2020
<Exec Command="dotnet tool install -g dotnet-serve" IgnoreExitCode="true" />
2121
</Target>
2222
<Target Name="RunSampleWithBrowser" DependsOnTargets="BuildSampleInTree;CheckServe">
23-
<Exec Command="$(_Dotnet) serve -o -d:bin/Release/AppBundle -p:8000" IgnoreExitCode="true" YieldDuringToolExecution="true" />
23+
<Exec Command="$(_Dotnet) serve -o -d:bin/$(Configuration)/AppBundle -p:8000" IgnoreExitCode="true" YieldDuringToolExecution="true" />
2424
</Target>
2525
</Project>

src/mono/wasm/runtime/startup.ts

+78-123
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
import { INTERNAL, Module, MONO, runtimeHelpers } from "./modules";
5-
import { AssetEntry, CharPtr, CharPtrNull, EmscriptenModuleMono, GlobalizationMode, MonoConfig, TypedArray, VoidPtr, wasm_type_symbol } from "./types";
5+
import { AllAssetEntryTypes, AssetEntry, CharPtr, CharPtrNull, EmscriptenModuleMono, GlobalizationMode, MonoConfig, TypedArray, VoidPtr, wasm_type_symbol } from "./types";
66
import cwraps from "./cwraps";
77
import { mono_wasm_raise_debug_event, mono_wasm_runtime_ready } from "./debug";
88
import { mono_wasm_globalization_init, mono_wasm_load_icu_data } from "./icu";
@@ -56,7 +56,7 @@ export function mono_wasm_set_runtime_options(options: string[]): void {
5656
cwraps.mono_wasm_parse_runtime_options(options.length, argv);
5757
}
5858

59-
function _handle_loaded_asset(ctx: MonoInitContext, asset: AssetEntry, url: string, blob: ArrayBuffer) {
59+
function _handle_fetched_asset(ctx: MonoInitContext, asset: AssetEntry, url: string, blob: ArrayBuffer) {
6060
const bytes = new Uint8Array(blob);
6161
if (ctx.tracing)
6262
console.log(`MONO_WASM: Loaded:${asset.name} as ${asset.behavior} size ${bytes.length} from ${url}`);
@@ -129,16 +129,6 @@ function _handle_loaded_asset(ctx: MonoInitContext, asset: AssetEntry, url: stri
129129
}
130130
}
131131

132-
// Initializes the runtime and loads assemblies, debug information, and other files.
133-
export function mono_load_runtime_and_bcl_args(args: MonoConfig): void {
134-
try {
135-
return _load_assets_and_runtime(args);
136-
} catch (exc: any) {
137-
console.error("MONO_WASM: Error in mono_load_runtime_and_bcl_args:", exc);
138-
throw exc;
139-
}
140-
}
141-
142132
function _apply_configuration_from_args(args: MonoConfig) {
143133
for (const k in (args.environment_variables || {}))
144134
mono_wasm_setenv(k, args.environment_variables![k]);
@@ -153,7 +143,7 @@ function _apply_configuration_from_args(args: MonoConfig) {
153143
mono_wasm_init_coverage_profiler(args.coverage_profiler_options);
154144
}
155145

156-
function _get_fetch_file_cb_from_args(args: MonoConfig): (asset: string) => Promise<Response> {
146+
function _get_fetch_implementation(args: MonoConfig): (asset: string) => Promise<Response> {
157147
if (typeof (args.fetch_file_cb) === "function")
158148
return args.fetch_file_cb;
159149

@@ -307,136 +297,101 @@ export function bindings_lazy_init(): void {
307297

308298
_create_primitive_converters();
309299
}
310-
function _load_assets_and_runtime(args: MonoConfig) {
311-
if (args.enable_debugging)
312-
args.debug_level = args.enable_debugging;
313-
314-
const ctx: MonoInitContext = {
315-
tracing: args.diagnostic_tracing || false,
316-
pending_count: args.assets.length,
317-
loaded_assets: Object.create(null),
318-
// dlls and pdbs, used by blazor and the debugger
319-
loaded_files: [],
320-
createPath: Module.FS_createPath,
321-
createDataFile: Module.FS_createDataFile
322-
};
323-
324-
if (ctx.tracing)
325-
console.log("MONO_WASM: mono_wasm_load_runtime_with_args", JSON.stringify(args));
326-
327-
_apply_configuration_from_args(args);
328300

329-
const fetch_file_cb = _get_fetch_file_cb_from_args(args);
330-
331-
const onPendingRequestComplete = function () {
332-
--ctx.pending_count;
333-
334-
if (ctx.pending_count === 0) {
335-
try {
336-
_finalize_startup(args, ctx);
337-
} catch (exc: any) {
338-
console.error("MONO_WASM: Unhandled exception in _finalize_startup", exc);
339-
console.error(exc.stack);
340-
throw exc;
341-
}
342-
}
343-
};
301+
// Initializes the runtime and loads assemblies, debug information, and other files.
302+
export async function mono_load_runtime_and_bcl_args(args: MonoConfig): Promise<void> {
303+
try {
304+
if (args.enable_debugging)
305+
args.debug_level = args.enable_debugging;
306+
307+
const ctx: MonoInitContext = {
308+
tracing: args.diagnostic_tracing || false,
309+
pending_count: args.assets.length,
310+
loaded_assets: Object.create(null),
311+
// dlls and pdbs, used by blazor and the debugger
312+
loaded_files: [],
313+
createPath: Module.FS_createPath,
314+
createDataFile: Module.FS_createDataFile
315+
};
344316

345-
const processFetchResponseBuffer = function (asset: AssetEntry, url: string, buffer: ArrayBuffer) {
346-
try {
347-
_handle_loaded_asset(ctx, asset, url, buffer);
348-
} catch (exc) {
349-
console.error(`MONO_WASM: Unhandled exception in processFetchResponseBuffer ${url} ${exc}`);
350-
throw exc;
351-
} finally {
352-
onPendingRequestComplete();
353-
}
354-
};
317+
_apply_configuration_from_args(args);
355318

356-
args.assets.forEach(function (asset: AssetEntry) {
357-
let sourceIndex = 0;
358-
const sourcesList = asset.load_remote ? args.remote_sources! : [""];
319+
const local_fetch = _get_fetch_implementation(args);
359320

360-
const handleFetchResponse = function (response: Response) {
361-
if (!response.ok) {
362-
try {
363-
attemptNextSource();
364-
return;
365-
} catch (exc) {
366-
console.error(`MONO_WASM: Unhandled exception in handleFetchResponse attemptNextSource for asset ${asset.name} ${exc}`);
367-
throw exc;
368-
}
369-
}
321+
const load_asset = async (asset: AllAssetEntryTypes): Promise<void> => {
322+
//TODO we could do module.addRunDependency(asset.name) and delay emscripten run() after all assets are loaded
370323

371-
try {
372-
const bufferPromise = response.arrayBuffer();
373-
bufferPromise.then((data) => processFetchResponseBuffer(asset, response.url, data));
374-
} catch (exc) {
375-
console.error(`MONO_WASM: Unhandled exception in handleFetchResponse for asset ${asset.name} ${exc}`);
376-
attemptNextSource();
377-
}
378-
};
324+
const sourcesList = asset.load_remote ? args.remote_sources! : [""];
325+
let error = undefined;
326+
for (let sourcePrefix of sourcesList) {
327+
// HACK: Special-case because MSBuild doesn't allow "" as an attribute
328+
if (sourcePrefix === "./")
329+
sourcePrefix = "";
379330

380-
const attemptNextSource = function () {
381-
if (sourceIndex >= sourcesList.length) {
382-
const msg = `MONO_WASM: Failed to load ${asset.name}`;
383-
try {
384-
const isOk = asset.is_optional ||
385-
(asset.name.match(/\.pdb$/) && args.ignore_pdb_load_errors);
386-
387-
if (isOk)
388-
console.debug(msg);
389-
else {
390-
console.error(msg);
391-
throw new Error(msg);
331+
let attemptUrl;
332+
if (sourcePrefix.trim() === "") {
333+
if (asset.behavior === "assembly")
334+
attemptUrl = locateFile(args.assembly_root + "/" + asset.name);
335+
else if (asset.behavior === "resource") {
336+
const path = asset.culture !== "" ? `${asset.culture}/${asset.name}` : asset.name;
337+
attemptUrl = locateFile(args.assembly_root + "/" + path);
392338
}
393-
} finally {
394-
onPendingRequestComplete();
395-
}
396-
}
397-
398-
let sourcePrefix = sourcesList[sourceIndex];
399-
sourceIndex++;
400-
401-
// HACK: Special-case because MSBuild doesn't allow "" as an attribute
402-
if (sourcePrefix === "./")
403-
sourcePrefix = "";
404-
405-
let attemptUrl;
406-
if (sourcePrefix.trim() === "") {
407-
if (asset.behavior === "assembly")
408-
attemptUrl = locateFile(args.assembly_root + "/" + asset.name);
409-
else if (asset.behavior === "resource") {
410-
const path = asset.culture !== "" ? `${asset.culture}/${asset.name}` : asset.name;
411-
attemptUrl = locateFile(args.assembly_root + "/" + path);
339+
else
340+
attemptUrl = asset.name;
341+
} else {
342+
attemptUrl = sourcePrefix + asset.name;
412343
}
413-
else
414-
attemptUrl = asset.name;
415-
} else {
416-
attemptUrl = sourcePrefix + asset.name;
417-
}
418-
419-
try {
420344
if (asset.name === attemptUrl) {
421345
if (ctx.tracing)
422346
console.log(`MONO_WASM: Attempting to fetch '${attemptUrl}'`);
423347
} else {
424348
if (ctx.tracing)
425349
console.log(`MONO_WASM: Attempting to fetch '${attemptUrl}' for ${asset.name}`);
426350
}
427-
const fetch_promise = fetch_file_cb(attemptUrl);
428-
fetch_promise.then(handleFetchResponse);
429-
} catch (exc) {
430-
console.error(`MONO_WASM: Error fetching ${attemptUrl} ${exc}`);
431-
attemptNextSource();
351+
try {
352+
const response = await local_fetch(attemptUrl);
353+
if (!response.ok) {
354+
error = new Error(`MONO_WASM: Fetch '${attemptUrl}' for ${asset.name} failed ${response.status} ${response.statusText}`);
355+
continue;// next source
356+
}
357+
358+
const buffer = await response.arrayBuffer();
359+
_handle_fetched_asset(ctx, asset, attemptUrl, buffer);
360+
--ctx.pending_count;
361+
error = undefined;
362+
}
363+
catch (err) {
364+
error = new Error(`MONO_WASM: Fetch '${attemptUrl}' for ${asset.name} failed ${err}`);
365+
continue; //next source
366+
}
367+
368+
if (!error) {
369+
//TODO Module.removeRunDependency(configFilePath);
370+
break; // this source worked, stop searching
371+
}
372+
}
373+
if (error) {
374+
const isOkToFail = asset.is_optional || (asset.name.match(/\.pdb$/) && args.ignore_pdb_load_errors);
375+
if (!isOkToFail)
376+
throw error;
432377
}
433378
};
379+
const fetch_promises: Promise<void>[] = [];
380+
// start fetching all assets in parallel
381+
for (const asset of args.assets) {
382+
fetch_promises.push(load_asset(asset));
383+
}
434384

435-
attemptNextSource();
436-
});
385+
await Promise.all(fetch_promises);
386+
387+
_finalize_startup(args, ctx);
388+
} catch (exc: any) {
389+
console.error("MONO_WASM: Error in mono_load_runtime_and_bcl_args:", exc);
390+
throw exc;
391+
}
437392
}
438393

439-
// used from ASP.NET
394+
// used from Blazor
440395
export function mono_wasm_load_data_archive(data: TypedArray, prefix: string): boolean {
441396
if (data.length < 8)
442397
return false;

src/mono/wasm/runtime/types.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export function coerceNull<T extends ManagedPointer | NativePointer>(ptr: T | nu
7575
export type MonoConfig = {
7676
isError: false,
7777
assembly_root: string, // the subfolder containing managed assemblies and pdbs
78-
assets: (AssetEntry | AssemblyEntry | SatelliteAssemblyEntry | VfsEntry | IcuData)[], // a list of assets to load along with the runtime. each asset is a dictionary-style Object with the following properties:
78+
assets: AllAssetEntryTypes[], // a list of assets to load along with the runtime. each asset is a dictionary-style Object with the following properties:
7979
debug_level?: number, // Either this or the next one needs to be set
8080
enable_debugging?: number, // Either this or the previous one needs to be set
8181
fetch_file_cb?: Request, // a function (string) invoked to fetch a given file. If no callback is provided a default implementation appropriate for the current environment will be selected (readFileSync in node, fetch elsewhere). If no default implementation is available this call will fail.
@@ -97,6 +97,8 @@ export type MonoConfigError = {
9797
error: any
9898
}
9999

100+
export type AllAssetEntryTypes = AssetEntry | AssemblyEntry | SatelliteAssemblyEntry | VfsEntry | IcuData;
101+
100102
// Types of assets that can be in the mono-config.js/mono-config.json file (taken from /src/tasks/WasmAppBuilder/WasmAppBuilder.cs)
101103
export type AssetEntry = {
102104
name: string, // the name of the asset, including extension.

0 commit comments

Comments
 (0)