Skip to content

[wasm] parallel asset loading #61610

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 1 commit into from
Nov 15, 2021
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
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