From b2b14029707dd19dfcbb2ac742c43784d0433848 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Fri, 13 Oct 2023 18:40:46 +0200 Subject: [PATCH] start MonoVM when essential DLLs are downloaded and continue downloading the rest --- src/mono/mono/metadata/bundled-resources.c | 5 +- src/mono/wasm/runtime/assets.ts | 2 +- src/mono/wasm/runtime/globals.ts | 4 +- src/mono/wasm/runtime/loader/assets.ts | 152 ++++++++++++++------- src/mono/wasm/runtime/startup.ts | 9 +- src/mono/wasm/runtime/types/internal.ts | 5 +- 6 files changed, 122 insertions(+), 55 deletions(-) diff --git a/src/mono/mono/metadata/bundled-resources.c b/src/mono/mono/metadata/bundled-resources.c index 9eae4f5db8fa8..ad2541547523d 100644 --- a/src/mono/mono/metadata/bundled-resources.c +++ b/src/mono/mono/metadata/bundled-resources.c @@ -126,8 +126,9 @@ bundled_resources_resource_id_hash (const char *id) void mono_bundled_resources_add (MonoBundledResource **resources_to_bundle, uint32_t len) { - MonoDomain *domain = mono_get_root_domain (); - g_assert (!domain); + // TODO FIXME, why this was necessary ? + // MonoDomain *domain = mono_get_root_domain (); + // g_assert (!domain); if (!bundled_resources) bundled_resources = g_hash_table_new_full ((GHashFunc)bundled_resources_resource_id_hash, (GEqualFunc)bundled_resources_resource_id_equal, NULL, bundled_resources_value_destroy_func); diff --git a/src/mono/wasm/runtime/assets.ts b/src/mono/wasm/runtime/assets.ts index d7c5c82b4d831..e8278fc4a95a0 100644 --- a/src/mono/wasm/runtime/assets.ts +++ b/src/mono/wasm/runtime/assets.ts @@ -106,7 +106,7 @@ export async function instantiate_symbols_asset(pendingAsset: AssetEntryInternal export async function wait_for_all_assets() { // wait for all assets in memory - await runtimeHelpers.allAssetsInMemory.promise; + await runtimeHelpers.remainingAssetsInMemory.promise; if (runtimeHelpers.config.assets) { mono_assert(loaderHelpers.actual_downloaded_assets_count == loaderHelpers.expected_downloaded_assets_count, () => `Expected ${loaderHelpers.expected_downloaded_assets_count} assets to be downloaded, but only finished ${loaderHelpers.actual_downloaded_assets_count}`); mono_assert(loaderHelpers.actual_instantiated_assets_count == loaderHelpers.expected_instantiated_assets_count, () => `Expected ${loaderHelpers.expected_instantiated_assets_count} assets to be in memory, but only instantiated ${loaderHelpers.actual_instantiated_assets_count}`); diff --git a/src/mono/wasm/runtime/globals.ts b/src/mono/wasm/runtime/globals.ts index 5554344dd5b11..ac6fd9271470b 100644 --- a/src/mono/wasm/runtime/globals.ts +++ b/src/mono/wasm/runtime/globals.ts @@ -64,7 +64,9 @@ export function setRuntimeGlobals(globalObjects: GlobalObjects) { Object.assign(runtimeHelpers, { gitHash, - allAssetsInMemory: createPromiseController(), + coreAssetsInMemory: createPromiseController(), + remainingAssetsInMemory: createPromiseController(), + AssetsInMemory: createPromiseController(), dotnetReady: createPromiseController(), afterInstantiateWasm: createPromiseController(), beforePreInit: createPromiseController(), diff --git a/src/mono/wasm/runtime/loader/assets.ts b/src/mono/wasm/runtime/loader/assets.ts index cc864a439afe4..3636acf2ee72a 100644 --- a/src/mono/wasm/runtime/loader/assets.ts +++ b/src/mono/wasm/runtime/loader/assets.ts @@ -17,10 +17,18 @@ import { makeURLAbsoluteWithApplicationBase } from "./polyfills"; let throttlingPromise: PromiseAndController | undefined; // in order to prevent net::ERR_INSUFFICIENT_RESOURCES if we start downloading too many files at same time let parallel_count = 0; +const coreAssemblies: AssetEntryInternal[] = []; const containedInSnapshotAssets: AssetEntryInternal[] = []; const alwaysLoadedAssets: AssetEntryInternal[] = []; const singleAssets: Map = new Map(); +// assemblies necessary to start Mono VM +const coreAssemblyNames: string[] = [ + "System.Private.CoreLib", + "System.Collections", + "System.Runtime.InteropServices.JavaScript", +]; + const jsRuntimeModulesAssetTypes: { [k: string]: boolean } = { @@ -151,7 +159,8 @@ export async function mono_download_assets(): Promise { loaderHelpers.maxParallelDownloads = loaderHelpers.config.maxParallelDownloads || loaderHelpers.maxParallelDownloads; loaderHelpers.enableDownloadRetry = loaderHelpers.config.enableDownloadRetry || loaderHelpers.enableDownloadRetry; try { - const promises_of_assets: Promise[] = []; + const promises_of_assets_core: Promise[] = []; + const promises_of_assets_remaining: Promise[] = []; const countAndStartDownload = (asset: AssetEntryInternal) => { if (!skipInstantiateByAssetTypes[asset.behavior] && shouldLoadIcuAsset(asset)) { @@ -159,7 +168,11 @@ export async function mono_download_assets(): Promise { } if (!skipDownloadsByAssetTypes[asset.behavior] && shouldLoadIcuAsset(asset)) { loaderHelpers.expected_downloaded_assets_count++; - promises_of_assets.push(start_asset_download(asset)); + if (asset.isCoreAssembly) { + promises_of_assets_core.push(start_asset_download(asset)); + } else { + promises_of_assets_remaining.push(start_asset_download(asset)); + } } }; @@ -172,7 +185,7 @@ export async function mono_download_assets(): Promise { await loaderHelpers.memorySnapshotSkippedOrDone.promise; // start fetching assets in parallel, only if memory snapshot is not available. - for (const asset of containedInSnapshotAssets) { + const loadOrSkipAsset = (asset: AssetEntryInternal) => { if (!runtimeHelpers.loadedMemorySnapshotSize) { countAndStartDownload(asset); } else { @@ -187,6 +200,12 @@ export async function mono_download_assets(): Promise { loaderHelpers._loaded_files.push({ url: url, file: virtualName }); } } + }; + for (const asset of coreAssemblies) { + loadOrSkipAsset(asset); + } + for (const asset of containedInSnapshotAssets) { + loadOrSkipAsset(asset); } loaderHelpers.allDownloadsQueued.promise_control.resolve(); @@ -194,52 +213,66 @@ export async function mono_download_assets(): Promise { // continue after the dotnet.runtime.js was loaded await loaderHelpers.runtimeModuleLoaded.promise; - const promises_of_asset_instantiation: Promise[] = []; - for (const downloadPromise of promises_of_assets) { - promises_of_asset_instantiation.push((async () => { - const asset = await downloadPromise; - if (asset.buffer) { - if (!skipInstantiateByAssetTypes[asset.behavior]) { - mono_assert(asset.buffer && typeof asset.buffer === "object", "asset buffer must be array-like or buffer-like or promise of these"); - mono_assert(typeof asset.resolvedUrl === "string", "resolvedUrl must be string"); - const url = asset.resolvedUrl!; - const buffer = await asset.buffer; - const data = new Uint8Array(buffer); - cleanupAsset(asset); - - // wait till after onRuntimeInitialized and after memory snapshot is loaded or skipped - - await runtimeHelpers.beforeOnRuntimeInitialized.promise; - runtimeHelpers.instantiate_asset(asset, url, data); + const instantiate = async (downloadPromise: Promise) => { + const asset = await downloadPromise; + if (asset.buffer) { + if (!skipInstantiateByAssetTypes[asset.behavior]) { + mono_assert(asset.buffer && typeof asset.buffer === "object", "asset buffer must be array-like or buffer-like or promise of these"); + mono_assert(typeof asset.resolvedUrl === "string", "resolvedUrl must be string"); + const url = asset.resolvedUrl!; + const buffer = await asset.buffer; + const data = new Uint8Array(buffer); + cleanupAsset(asset); + + // wait till after onRuntimeInitialized and after memory snapshot is loaded or skipped + + await runtimeHelpers.beforeOnRuntimeInitialized.promise; + runtimeHelpers.instantiate_asset(asset, url, data); + } + } else { + const headersOnly = skipBufferByAssetTypes[asset.behavior]; + if (!headersOnly) { + mono_assert(asset.isOptional, "Expected asset to have the downloaded buffer"); + if (!skipDownloadsByAssetTypes[asset.behavior] && shouldLoadIcuAsset(asset)) { + loaderHelpers.expected_downloaded_assets_count--; + } + if (!skipInstantiateByAssetTypes[asset.behavior] && shouldLoadIcuAsset(asset)) { + loaderHelpers.expected_instantiated_assets_count--; } } else { - const headersOnly = skipBufferByAssetTypes[asset.behavior]; - if (!headersOnly) { - mono_assert(asset.isOptional, "Expected asset to have the downloaded buffer"); - if (!skipDownloadsByAssetTypes[asset.behavior] && shouldLoadIcuAsset(asset)) { - loaderHelpers.expected_downloaded_assets_count--; - } - if (!skipInstantiateByAssetTypes[asset.behavior] && shouldLoadIcuAsset(asset)) { - loaderHelpers.expected_instantiated_assets_count--; - } - } else { - if (asset.behavior === "symbols") { - await runtimeHelpers.instantiate_symbols_asset(asset); - cleanupAsset(asset); - } - - if (skipBufferByAssetTypes[asset.behavior]) { - ++loaderHelpers.actual_downloaded_assets_count; - } + if (asset.behavior === "symbols") { + await runtimeHelpers.instantiate_symbols_asset(asset); + cleanupAsset(asset); + } + + if (skipBufferByAssetTypes[asset.behavior]) { + ++loaderHelpers.actual_downloaded_assets_count; } } - })()); + } + }; + + const promises_of_asset_instantiation_core: Promise[] = []; + const promises_of_asset_instantiation_remaining: Promise[] = []; + for (const downloadPromise of promises_of_assets_core) { + promises_of_asset_instantiation_core.push(instantiate(downloadPromise)); + } + for (const downloadPromise of promises_of_assets_remaining) { + promises_of_asset_instantiation_remaining.push(instantiate(downloadPromise)); } // this await will get past the onRuntimeInitialized because we are not blocking via addRunDependency - // and we are not awating it here - Promise.all(promises_of_asset_instantiation).then(() => { - runtimeHelpers.allAssetsInMemory.promise_control.resolve(); + // and we are not awaiting it here + Promise.all(promises_of_asset_instantiation_core).then(() => { + runtimeHelpers.coreAssetsInMemory.promise_control.resolve(); + }).catch(err => { + loaderHelpers.err("Error in mono_download_assets: " + err); + mono_exit(1, err); + throw err; + }); + Promise.all(promises_of_asset_instantiation_remaining).then(async () => { + await runtimeHelpers.coreAssetsInMemory.promise; + runtimeHelpers.remainingAssetsInMemory.promise_control.resolve(); }).catch(err => { loaderHelpers.err("Error in mono_download_assets: " + err); mono_exit(1, err); @@ -255,6 +288,12 @@ export async function mono_download_assets(): Promise { } } +const noExtRx = /\.[^/.]+$/; +function isCoreAssembly(assemblyName: string): boolean { + const nameWithoutExtension = assemblyName.replace(noExtRx, ""); + return coreAssemblyNames.includes(nameWithoutExtension); +} + export function prepareAssets() { const config = loaderHelpers.config; const modulesAssets: AssetEntryInternal[] = []; @@ -269,7 +308,12 @@ export function prepareAssets() { mono_assert(!asset.hash || typeof asset.hash === "string", "asset resolvedUrl could be string"); mono_assert(!asset.pendingDownload || typeof asset.pendingDownload === "object", "asset pendingDownload could be object"); if (containedInSnapshotByAssetTypes[asset.behavior]) { - containedInSnapshotAssets.push(asset); + if ((asset.behavior === "assembly" || asset.behavior === "pdb") && isCoreAssembly(asset.name)) { + asset.isCoreAssembly = true; + coreAssemblies.push(asset); + } else { + containedInSnapshotAssets.push(asset); + } } else { alwaysLoadedAssets.push(asset); } @@ -291,21 +335,33 @@ export function prepareAssets() { if (resources.assembly) { for (const name in resources.assembly) { - containedInSnapshotAssets.push({ + const asset: AssetEntryInternal = { name, hash: resources.assembly[name], behavior: "assembly" - }); + }; + if (isCoreAssembly(name)) { + asset.isCoreAssembly = true; + coreAssemblies.push(asset); + } else { + containedInSnapshotAssets.push(asset); + } } } if (config.debugLevel != 0 && resources.pdb) { for (const name in resources.pdb) { - containedInSnapshotAssets.push({ + const asset: AssetEntryInternal = { name, hash: resources.pdb[name], behavior: "pdb" - }); + }; + if (isCoreAssembly(name)) { + asset.isCoreAssembly = true; + coreAssemblies.push(asset); + } else { + containedInSnapshotAssets.push(asset); + } } } @@ -378,7 +434,7 @@ export function prepareAssets() { } } - config.assets = [...containedInSnapshotAssets, ...alwaysLoadedAssets, ...modulesAssets]; + config.assets = [...coreAssemblies, ...containedInSnapshotAssets, ...alwaysLoadedAssets, ...modulesAssets]; } export function prepareAssetsWorker() { diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index 4b209c8f1fa3a..6e5b1518766e9 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -246,8 +246,6 @@ async function onRuntimeInitializedAsync(userOnRuntimeInitialized: () => void) { // signal this stage, this will allow pending assets to allocate memory runtimeHelpers.beforeOnRuntimeInitialized.promise_control.resolve(); - await wait_for_all_assets(); - // Threads early are not supported with memory snapshot. See below how we enable them later. // Please disable startupMemoryCache in order to be able to diagnose or pause runtime startup. if (MonoWasmThreads && !runtimeHelpers.config.startupMemoryCache) { @@ -538,6 +536,9 @@ async function mono_wasm_before_memory_snapshot() { if (runtimeHelpers.config.browserProfilerOptions) mono_wasm_init_browser_profiler(runtimeHelpers.config.browserProfilerOptions); + // wait for all assets in memory + await runtimeHelpers.coreAssetsInMemory.promise; + mono_wasm_load_runtime("unused", runtimeHelpers.config.debugLevel); if (runtimeHelpers.config.virtualWorkingDirectory) { @@ -551,6 +552,8 @@ async function mono_wasm_before_memory_snapshot() { FS.chdir(cwd); } + await wait_for_all_assets(); + // we didn't have snapshot yet and the feature is enabled. Take snapshot now. if (runtimeHelpers.config.startupMemoryCache) { await storeMemorySnapshot(localHeapViewU8().buffer); @@ -571,6 +574,8 @@ export function mono_wasm_load_runtime(unused?: string, debugLevel?: number): vo } } cwraps.mono_wasm_load_runtime(unused || "unused", debugLevel); + runtimeHelpers.monoReady = true; + endMeasure(mark, MeasuredBlock.loadRuntime); } catch (err: any) { diff --git a/src/mono/wasm/runtime/types/internal.ts b/src/mono/wasm/runtime/types/internal.ts index 8167bb328b824..33b73201a15cb 100644 --- a/src/mono/wasm/runtime/types/internal.ts +++ b/src/mono/wasm/runtime/types/internal.ts @@ -97,6 +97,7 @@ export interface AssetEntryInternal extends AssetEntry { pendingDownloadInternal?: LoadingResource noCache?: boolean useCredentials?: boolean + isCoreAssembly?: boolean } export type LoaderHelpers = { @@ -189,11 +190,13 @@ export type RuntimeHelpers = { memorySnapshotCacheKey: string, subtle: SubtleCrypto | null, updateMemoryViews: () => void + monoReady: boolean, runtimeReady: boolean, jsSynchronizationContextInstalled: boolean, cspPolicy: boolean, - allAssetsInMemory: PromiseAndController, + coreAssetsInMemory: PromiseAndController, + remainingAssetsInMemory: PromiseAndController, dotnetReady: PromiseAndController, afterInstantiateWasm: PromiseAndController, beforePreInit: PromiseAndController,