Skip to content

Commit

Permalink
[wasm] parallel asset loading (#61610)
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelsavara authored Nov 15, 2021
1 parent fdafc7c commit 5d3e70a
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 125 deletions.
2 changes: 1 addition & 1 deletion src/mono/sample/wasm/Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@
<Exec Command="dotnet tool install -g dotnet-serve" IgnoreExitCode="true" />
</Target>
<Target Name="RunSampleWithBrowser" DependsOnTargets="BuildSampleInTree;CheckServe">
<Exec Command="$(_Dotnet) serve -o -d:bin/Release/AppBundle -p:8000" IgnoreExitCode="true" YieldDuringToolExecution="true" />
<Exec Command="$(_Dotnet) serve -o -d:bin/$(Configuration)/AppBundle -p:8000" IgnoreExitCode="true" YieldDuringToolExecution="true" />
</Target>
</Project>
201 changes: 78 additions & 123 deletions src/mono/wasm/runtime/startup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

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

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

// Initializes the runtime and loads assemblies, debug information, and other files.
export function mono_load_runtime_and_bcl_args(args: MonoConfig): void {
try {
return _load_assets_and_runtime(args);
} catch (exc: any) {
console.error("MONO_WASM: Error in mono_load_runtime_and_bcl_args:", exc);
throw exc;
}
}

function _apply_configuration_from_args(args: MonoConfig) {
for (const k in (args.environment_variables || {}))
mono_wasm_setenv(k, args.environment_variables![k]);
Expand All @@ -153,7 +143,7 @@ function _apply_configuration_from_args(args: MonoConfig) {
mono_wasm_init_coverage_profiler(args.coverage_profiler_options);
}

function _get_fetch_file_cb_from_args(args: MonoConfig): (asset: string) => Promise<Response> {
function _get_fetch_implementation(args: MonoConfig): (asset: string) => Promise<Response> {
if (typeof (args.fetch_file_cb) === "function")
return args.fetch_file_cb;

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

_create_primitive_converters();
}
function _load_assets_and_runtime(args: MonoConfig) {
if (args.enable_debugging)
args.debug_level = args.enable_debugging;

const ctx: MonoInitContext = {
tracing: args.diagnostic_tracing || false,
pending_count: args.assets.length,
loaded_assets: Object.create(null),
// dlls and pdbs, used by blazor and the debugger
loaded_files: [],
createPath: Module.FS_createPath,
createDataFile: Module.FS_createDataFile
};

if (ctx.tracing)
console.log("MONO_WASM: mono_wasm_load_runtime_with_args", JSON.stringify(args));

_apply_configuration_from_args(args);

const fetch_file_cb = _get_fetch_file_cb_from_args(args);

const onPendingRequestComplete = function () {
--ctx.pending_count;

if (ctx.pending_count === 0) {
try {
_finalize_startup(args, ctx);
} catch (exc: any) {
console.error("MONO_WASM: Unhandled exception in _finalize_startup", exc);
console.error(exc.stack);
throw exc;
}
}
};
// Initializes the runtime and loads assemblies, debug information, and other files.
export async function mono_load_runtime_and_bcl_args(args: MonoConfig): Promise<void> {
try {
if (args.enable_debugging)
args.debug_level = args.enable_debugging;

const ctx: MonoInitContext = {
tracing: args.diagnostic_tracing || false,
pending_count: args.assets.length,
loaded_assets: Object.create(null),
// dlls and pdbs, used by blazor and the debugger
loaded_files: [],
createPath: Module.FS_createPath,
createDataFile: Module.FS_createDataFile
};

const processFetchResponseBuffer = function (asset: AssetEntry, url: string, buffer: ArrayBuffer) {
try {
_handle_loaded_asset(ctx, asset, url, buffer);
} catch (exc) {
console.error(`MONO_WASM: Unhandled exception in processFetchResponseBuffer ${url} ${exc}`);
throw exc;
} finally {
onPendingRequestComplete();
}
};
_apply_configuration_from_args(args);

args.assets.forEach(function (asset: AssetEntry) {
let sourceIndex = 0;
const sourcesList = asset.load_remote ? args.remote_sources! : [""];
const local_fetch = _get_fetch_implementation(args);

const handleFetchResponse = function (response: Response) {
if (!response.ok) {
try {
attemptNextSource();
return;
} catch (exc) {
console.error(`MONO_WASM: Unhandled exception in handleFetchResponse attemptNextSource for asset ${asset.name} ${exc}`);
throw exc;
}
}
const load_asset = async (asset: AllAssetEntryTypes): Promise<void> => {
//TODO we could do module.addRunDependency(asset.name) and delay emscripten run() after all assets are loaded

try {
const bufferPromise = response.arrayBuffer();
bufferPromise.then((data) => processFetchResponseBuffer(asset, response.url, data));
} catch (exc) {
console.error(`MONO_WASM: Unhandled exception in handleFetchResponse for asset ${asset.name} ${exc}`);
attemptNextSource();
}
};
const sourcesList = asset.load_remote ? args.remote_sources! : [""];
let error = undefined;
for (let sourcePrefix of sourcesList) {
// HACK: Special-case because MSBuild doesn't allow "" as an attribute
if (sourcePrefix === "./")
sourcePrefix = "";

const attemptNextSource = function () {
if (sourceIndex >= sourcesList.length) {
const msg = `MONO_WASM: Failed to load ${asset.name}`;
try {
const isOk = asset.is_optional ||
(asset.name.match(/\.pdb$/) && args.ignore_pdb_load_errors);

if (isOk)
console.debug(msg);
else {
console.error(msg);
throw new Error(msg);
let attemptUrl;
if (sourcePrefix.trim() === "") {
if (asset.behavior === "assembly")
attemptUrl = locateFile(args.assembly_root + "/" + asset.name);
else if (asset.behavior === "resource") {
const path = asset.culture !== "" ? `${asset.culture}/${asset.name}` : asset.name;
attemptUrl = locateFile(args.assembly_root + "/" + path);
}
} finally {
onPendingRequestComplete();
}
}

let sourcePrefix = sourcesList[sourceIndex];
sourceIndex++;

// HACK: Special-case because MSBuild doesn't allow "" as an attribute
if (sourcePrefix === "./")
sourcePrefix = "";

let attemptUrl;
if (sourcePrefix.trim() === "") {
if (asset.behavior === "assembly")
attemptUrl = locateFile(args.assembly_root + "/" + asset.name);
else if (asset.behavior === "resource") {
const path = asset.culture !== "" ? `${asset.culture}/${asset.name}` : asset.name;
attemptUrl = locateFile(args.assembly_root + "/" + path);
else
attemptUrl = asset.name;
} else {
attemptUrl = sourcePrefix + asset.name;
}
else
attemptUrl = asset.name;
} else {
attemptUrl = sourcePrefix + asset.name;
}

try {
if (asset.name === attemptUrl) {
if (ctx.tracing)
console.log(`MONO_WASM: Attempting to fetch '${attemptUrl}'`);
} else {
if (ctx.tracing)
console.log(`MONO_WASM: Attempting to fetch '${attemptUrl}' for ${asset.name}`);
}
const fetch_promise = fetch_file_cb(attemptUrl);
fetch_promise.then(handleFetchResponse);
} catch (exc) {
console.error(`MONO_WASM: Error fetching ${attemptUrl} ${exc}`);
attemptNextSource();
try {
const response = await local_fetch(attemptUrl);
if (!response.ok) {
error = new Error(`MONO_WASM: Fetch '${attemptUrl}' for ${asset.name} failed ${response.status} ${response.statusText}`);
continue;// next source
}

const buffer = await response.arrayBuffer();
_handle_fetched_asset(ctx, asset, attemptUrl, buffer);
--ctx.pending_count;
error = undefined;
}
catch (err) {
error = new Error(`MONO_WASM: Fetch '${attemptUrl}' for ${asset.name} failed ${err}`);
continue; //next source
}

if (!error) {
//TODO Module.removeRunDependency(configFilePath);
break; // this source worked, stop searching
}
}
if (error) {
const isOkToFail = asset.is_optional || (asset.name.match(/\.pdb$/) && args.ignore_pdb_load_errors);
if (!isOkToFail)
throw error;
}
};
const fetch_promises: Promise<void>[] = [];
// start fetching all assets in parallel
for (const asset of args.assets) {
fetch_promises.push(load_asset(asset));
}

attemptNextSource();
});
await Promise.all(fetch_promises);

_finalize_startup(args, ctx);
} catch (exc: any) {
console.error("MONO_WASM: Error in mono_load_runtime_and_bcl_args:", exc);
throw exc;
}
}

// used from ASP.NET
// used from Blazor
export function mono_wasm_load_data_archive(data: TypedArray, prefix: string): boolean {
if (data.length < 8)
return false;
Expand Down
4 changes: 3 additions & 1 deletion src/mono/wasm/runtime/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export function coerceNull<T extends ManagedPointer | NativePointer>(ptr: T | nu
export type MonoConfig = {
isError: false,
assembly_root: string, // the subfolder containing managed assemblies and pdbs
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:
assets: AllAssetEntryTypes[], // a list of assets to load along with the runtime. each asset is a dictionary-style Object with the following properties:
debug_level?: number, // Either this or the next one needs to be set
enable_debugging?: number, // Either this or the previous one needs to be set
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.
Expand All @@ -97,6 +97,8 @@ export type MonoConfigError = {
error: any
}

export type AllAssetEntryTypes = AssetEntry | AssemblyEntry | SatelliteAssemblyEntry | VfsEntry | IcuData;

// Types of assets that can be in the mono-config.js/mono-config.json file (taken from /src/tasks/WasmAppBuilder/WasmAppBuilder.cs)
export type AssetEntry = {
name: string, // the name of the asset, including extension.
Expand Down

0 comments on commit 5d3e70a

Please sign in to comment.