Skip to content
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

[browser] start MonoVM during DLL download #93480

Closed
Closed
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
5 changes: 3 additions & 2 deletions src/mono/mono/metadata/bundled-resources.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/mono/wasm/runtime/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`);
Expand Down
4 changes: 3 additions & 1 deletion src/mono/wasm/runtime/globals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ export function setRuntimeGlobals(globalObjects: GlobalObjects) {

Object.assign(runtimeHelpers, {
gitHash,
allAssetsInMemory: createPromiseController<void>(),
coreAssetsInMemory: createPromiseController<void>(),
remainingAssetsInMemory: createPromiseController<void>(),
AssetsInMemory: createPromiseController<void>(),
dotnetReady: createPromiseController<any>(),
afterInstantiateWasm: createPromiseController<void>(),
beforePreInit: createPromiseController<void>(),
Expand Down
152 changes: 104 additions & 48 deletions src/mono/wasm/runtime/loader/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,18 @@ import { makeURLAbsoluteWithApplicationBase } from "./polyfills";
let throttlingPromise: PromiseAndController<void> | 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<string, AssetEntryInternal> = 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
} = {
Expand Down Expand Up @@ -151,15 +159,20 @@ export async function mono_download_assets(): Promise<void> {
loaderHelpers.maxParallelDownloads = loaderHelpers.config.maxParallelDownloads || loaderHelpers.maxParallelDownloads;
loaderHelpers.enableDownloadRetry = loaderHelpers.config.enableDownloadRetry || loaderHelpers.enableDownloadRetry;
try {
const promises_of_assets: Promise<AssetEntryInternal>[] = [];
const promises_of_assets_core: Promise<AssetEntryInternal>[] = [];
const promises_of_assets_remaining: Promise<AssetEntryInternal>[] = [];

const countAndStartDownload = (asset: AssetEntryInternal) => {
if (!skipInstantiateByAssetTypes[asset.behavior] && shouldLoadIcuAsset(asset)) {
loaderHelpers.expected_instantiated_assets_count++;
}
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));
}
}
};

Expand All @@ -172,7 +185,7 @@ export async function mono_download_assets(): Promise<void> {
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 {
Expand All @@ -187,59 +200,79 @@ export async function mono_download_assets(): Promise<void> {
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();

// continue after the dotnet.runtime.js was loaded
await loaderHelpers.runtimeModuleLoaded.promise;

const promises_of_asset_instantiation: Promise<void>[] = [];
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<AssetEntryInternal>) => {
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<void>[] = [];
const promises_of_asset_instantiation_remaining: Promise<void>[] = [];
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);
Expand All @@ -255,6 +288,12 @@ export async function mono_download_assets(): Promise<void> {
}
}

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[] = [];
Expand All @@ -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);
}
Expand All @@ -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);
}
}
}

Expand Down Expand Up @@ -378,7 +434,7 @@ export function prepareAssets() {
}
}

config.assets = [...containedInSnapshotAssets, ...alwaysLoadedAssets, ...modulesAssets];
config.assets = [...coreAssemblies, ...containedInSnapshotAssets, ...alwaysLoadedAssets, ...modulesAssets];
}

export function prepareAssetsWorker() {
Expand Down
9 changes: 7 additions & 2 deletions src/mono/wasm/runtime/startup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
Expand All @@ -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) {
Expand Down
5 changes: 4 additions & 1 deletion src/mono/wasm/runtime/types/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ export interface AssetEntryInternal extends AssetEntry {
pendingDownloadInternal?: LoadingResource
noCache?: boolean
useCredentials?: boolean
isCoreAssembly?: boolean
}

export type LoaderHelpers = {
Expand Down Expand Up @@ -189,11 +190,13 @@ export type RuntimeHelpers = {
memorySnapshotCacheKey: string,
subtle: SubtleCrypto | null,
updateMemoryViews: () => void
monoReady: boolean,
runtimeReady: boolean,
jsSynchronizationContextInstalled: boolean,
cspPolicy: boolean,

allAssetsInMemory: PromiseAndController<void>,
coreAssetsInMemory: PromiseAndController<void>,
remainingAssetsInMemory: PromiseAndController<void>,
dotnetReady: PromiseAndController<any>,
afterInstantiateWasm: PromiseAndController<void>,
beforePreInit: PromiseAndController<void>,
Expand Down
Loading