From 1c17b78857a32ef7ab7819d9a1e5bae1839d9fc7 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Tue, 16 Jun 2026 17:36:50 +0200 Subject: [PATCH 01/15] fix MT --- src/mono/browser/build/BrowserWasmApp.targets | 3 ++ src/mono/browser/runtime/cwraps.ts | 2 + src/mono/browser/runtime/loader/config.ts | 2 +- src/mono/browser/runtime/loader/worker.ts | 15 +++++--- src/mono/browser/runtime/pthreads/index.ts | 2 +- src/mono/browser/runtime/pthreads/shared.ts | 5 --- .../browser/runtime/pthreads/ui-thread.ts | 37 +++++++++++-------- .../browser/runtime/pthreads/worker-thread.ts | 9 ++--- src/mono/browser/runtime/startup.ts | 5 ++- src/mono/browser/runtime/types/internal.ts | 4 +- .../Wasm.Browser.Threads.Sample.csproj | 4 ++ .../wasm/browser-threads/wwwroot/main.js | 1 + 12 files changed, 52 insertions(+), 37 deletions(-) diff --git a/src/mono/browser/build/BrowserWasmApp.targets b/src/mono/browser/build/BrowserWasmApp.targets index ea0b33bb83ebeb..32541ff00fd668 100644 --- a/src/mono/browser/build/BrowserWasmApp.targets +++ b/src/mono/browser/build/BrowserWasmApp.targets @@ -309,6 +309,9 @@ + + + <_WasmEHLib Condition="'$(WasmEnableExceptionHandling)' == 'true'">libmono-wasm-eh-wasm.a <_WasmEHLib Condition="'$(WasmEnableExceptionHandling)' != 'true'">libmono-wasm-eh-js.a diff --git a/src/mono/browser/runtime/cwraps.ts b/src/mono/browser/runtime/cwraps.ts index 1acc3e00204907..6a4f602484a990 100644 --- a/src/mono/browser/runtime/cwraps.ts +++ b/src/mono/browser/runtime/cwraps.ts @@ -27,6 +27,7 @@ const threading_cwraps: SigLine[] = WasmEnableThreads ? [ [true, "mono_wasm_print_thread_dump", "void", []], [true, "mono_wasm_synchronization_context_pump", "void", []], [true, "mono_threads_wasm_sync_run_in_target_thread_done", "void", ["number"]], + [true, "pthread_self", "number", []], ] : []; // when the method is assigned/cached at usage, instead of being invoked directly from cwraps, it can't be marked lazy, because it would be re-bound on each call @@ -149,6 +150,7 @@ export interface t_ThreadingCwraps { mono_wasm_print_thread_dump(): void; mono_wasm_synchronization_context_pump(): void; mono_threads_wasm_sync_run_in_target_thread_done(sem: VoidPtr): void; + pthread_self(): PThreadPtr; } export interface t_ProfilerCwraps { diff --git a/src/mono/browser/runtime/loader/config.ts b/src/mono/browser/runtime/loader/config.ts index 36128ce2230754..e0618be5918a46 100644 --- a/src/mono/browser/runtime/loader/config.ts +++ b/src/mono/browser/runtime/loader/config.ts @@ -201,7 +201,7 @@ export function normalizeConfig () { if (WasmEnableThreads) { if (!Number.isInteger(config.pthreadPoolInitialSize)) { - config.pthreadPoolInitialSize = 5; + config.pthreadPoolInitialSize = 7; } if (!Number.isInteger(config.pthreadPoolUnusedSize)) { config.pthreadPoolUnusedSize = 1; diff --git a/src/mono/browser/runtime/loader/worker.ts b/src/mono/browser/runtime/loader/worker.ts index c171211c0c6d8f..8d7b9f6f87a3c4 100644 --- a/src/mono/browser/runtime/loader/worker.ts +++ b/src/mono/browser/runtime/loader/worker.ts @@ -2,9 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. import { MonoConfigInternal, PThreadInfo, WorkerToMainMessageType, monoMessageSymbol } from "../types/internal"; -import { MonoConfig } from "../types"; import { deep_merge_config, normalizeConfig } from "./config"; -import { ENVIRONMENT_IS_WEB, loaderHelpers, runtimeHelpers } from "./globals"; +import { emscriptenModule, ENVIRONMENT_IS_WEB, loaderHelpers, runtimeHelpers } from "./globals"; import { mono_log_debug } from "./logging"; export function setupPreloadChannelToMainThread () { @@ -12,9 +11,7 @@ export function setupPreloadChannelToMainThread () { const workerPort = channel.port1; const mainPort = channel.port2; workerPort.addEventListener("message", (event) => { - const config = JSON.parse(event.data.config) as MonoConfig; - const monoThreadInfo = JSON.parse(event.data.monoThreadInfo) as PThreadInfo; - onMonoConfigReceived(config, monoThreadInfo); + onMonoConfigReceived(event.data); workerPort.close(); mainPort.close(); }, { once: true }); @@ -31,7 +28,13 @@ export function setupPreloadChannelToMainThread () { let workerMonoConfigReceived = false; // called when the main thread sends us the mono config -function onMonoConfigReceived (config: MonoConfigInternal, monoThreadInfo: PThreadInfo): void { +async function onMonoConfigReceived (data: any): Promise { + const config = JSON.parse(data.config) as MonoConfigInternal; + const monoThreadInfo = JSON.parse(data.monoThreadInfo) as PThreadInfo; + emscriptenModule.config = config; + emscriptenModule.wasmModule = data.wasmModule; + emscriptenModule.wasmMemory = data.wasmMemory; + emscriptenModule.handlers = data.handlers; if (workerMonoConfigReceived) { mono_log_debug("mono config already received"); return; diff --git a/src/mono/browser/runtime/pthreads/index.ts b/src/mono/browser/runtime/pthreads/index.ts index 484f66ddf3c9aa..953412e8043193 100644 --- a/src/mono/browser/runtime/pthreads/index.ts +++ b/src/mono/browser/runtime/pthreads/index.ts @@ -6,7 +6,7 @@ import { utf16ToString } from "../strings"; export { mono_wasm_main_thread_ptr, - mono_wasm_pthread_ptr, update_thread_info, isMonoThreadMessage, monoThreadInfo, + update_thread_info, isMonoThreadMessage, monoThreadInfo, } from "./shared"; export { SystemInteropJS_InstallWebWorkerInteropImpl, SystemInteropJS_UninstallWebWorkerInterop } from "./worker-interop"; export { diff --git a/src/mono/browser/runtime/pthreads/shared.ts b/src/mono/browser/runtime/pthreads/shared.ts index 93e62b0a289191..3596bcf47b4edf 100644 --- a/src/mono/browser/runtime/pthreads/shared.ts +++ b/src/mono/browser/runtime/pthreads/shared.ts @@ -89,11 +89,6 @@ export function mono_wasm_schedule_synchronization_context (): void { Module.safeSetTimeout(exec_synchronization_context_pump, 0); } -export function mono_wasm_pthread_ptr (): PThreadPtr { - if (!WasmEnableThreads) return PThreadPtrNull; - return (Module)["_pthread_self"](); -} - export function mono_wasm_main_thread_ptr (): PThreadPtr { if (!WasmEnableThreads) return PThreadPtrNull; return (Module)["_emscripten_main_runtime_thread_id"](); diff --git a/src/mono/browser/runtime/pthreads/ui-thread.ts b/src/mono/browser/runtime/pthreads/ui-thread.ts index b9036aecfbeae6..b15de7dd887230 100644 --- a/src/mono/browser/runtime/pthreads/ui-thread.ts +++ b/src/mono/browser/runtime/pthreads/ui-thread.ts @@ -5,10 +5,11 @@ import WasmEnableThreads from "consts:wasmEnableThreads"; import BuildConfiguration from "consts:configuration"; import { } from "../globals"; -import { MonoWorkerToMainMessage, monoThreadInfo, mono_wasm_pthread_ptr, update_thread_info, worker_empty_prefix } from "./shared"; +import { MonoWorkerToMainMessage, monoThreadInfo, update_thread_info, worker_empty_prefix } from "./shared"; import { Module, ENVIRONMENT_IS_WORKER, createPromiseController, loaderHelpers, mono_assert, runtimeHelpers } from "../globals"; import { PThreadLibrary, MainToWorkerMessageType, MonoThreadMessage, PThreadInfo, PThreadPtr, PThreadPtrNull, PThreadWorker, PromiseController, Thread, WorkerToMainMessageType, monoMessageSymbol } from "../types/internal"; import { mono_log_info, mono_log_debug, mono_log_warn } from "../logging"; +import { threads_c_functions as tcwraps } from "../cwraps"; const threadPromises: Map[]> = new Map(); @@ -53,7 +54,7 @@ export function resolveThreadPromises (pthreadPtr: PThreadPtr, thread?: Thread): } // handler that runs in the main thread when a message is received from a pthread worker -function monoWorkerMessageHandler (worker: PThreadWorker, ev: MessageEvent): void { +function monoWorkerMessageHandler (worker: PThreadWorker, wasmModule: WebAssembly.Module, ev: MessageEvent): void { if (!WasmEnableThreads) return; let pthreadId: PThreadPtr; // this is emscripten message @@ -74,6 +75,14 @@ function monoWorkerMessageHandler (worker: PThreadWorker, ev: MessageEvent) let thread: Thread; pthreadId = message.info?.pthreadId ?? 0; worker.info = Object.assign({}, worker.info, message.info); + const wasmMemory = runtimeHelpers.getMemory(); + const handlers = []; + const knownHandlers = ["onExit", "onAbort", "print", "printErr"]; + for (const handler of knownHandlers) { + if (Object.prototype.propertyIsEnumerable.call(Module, handler)) { + handlers.push(handler); + } + } switch (message.monoCmd) { case WorkerToMainMessageType.preload: // this one shot port from setupPreloadChannelToMainThread @@ -82,6 +91,9 @@ function monoWorkerMessageHandler (worker: PThreadWorker, ev: MessageEvent) cmd: MainToWorkerMessageType.applyConfig, config: JSON.stringify(runtimeHelpers.config), monoThreadInfo: JSON.stringify(worker.info), + handlers, + wasmMemory, + wasmModule }); break; case WorkerToMainMessageType.pthreadCreated: @@ -117,17 +129,6 @@ function monoWorkerMessageHandler (worker: PThreadWorker, ev: MessageEvent) } } -/// Called by Emscripten internals on the browser thread when a new pthread worker is created and added to the pthread worker pool. -/// At this point the worker doesn't have any pthread assigned to it, yet. -export function onWorkerLoadInitiated (worker: PThreadWorker, loaded: Promise): void { - if (!WasmEnableThreads) return; - worker.addEventListener("message", (ev) => monoWorkerMessageHandler(worker, ev)); - loaded.then(() => { - worker.info.isLoaded = true; - }); -} - - export async function populateEmscriptenPool (): Promise { if (!WasmEnableThreads) return; const unused = getUnusedWorkerPool(); @@ -142,7 +143,7 @@ export async function mono_wasm_init_threads () { if (!WasmEnableThreads) return; // setup the UI thread - runtimeHelpers.currentThreadTID = monoThreadInfo.pthreadId = mono_wasm_pthread_ptr(); + runtimeHelpers.currentThreadTID = monoThreadInfo.pthreadId = tcwraps.pthread_self(); monoThreadInfo.threadName = "UI Thread"; monoThreadInfo.isUI = true; monoThreadInfo.isRunning = true; @@ -210,9 +211,13 @@ export function replaceEmscriptenPThreadUI (modulePThread: PThreadLibrary): void const originalLoadWasmModuleToWorker = modulePThread.loadWasmModuleToWorker; const originalReturnWorkerToPool = modulePThread.returnWorkerToPool; - modulePThread.loadWasmModuleToWorker = (worker: PThreadWorker): Promise => { + modulePThread.loadWasmModuleToWorker = async (worker: PThreadWorker): Promise => { + const wasmModule = await loaderHelpers.wasmCompilePromise.promise; + worker.addEventListener("message", (ev) => monoWorkerMessageHandler(worker, wasmModule, ev)); const afterLoaded = originalLoadWasmModuleToWorker(worker); - onWorkerLoadInitiated(worker, afterLoaded); + afterLoaded.then(() => { + worker.info.isLoaded = true; + }); if (loaderHelpers.config.exitOnUnhandledError) { worker.onerror = (e) => { loaderHelpers.mono_exit(1, e); diff --git a/src/mono/browser/runtime/pthreads/worker-thread.ts b/src/mono/browser/runtime/pthreads/worker-thread.ts index 5b2a0b551ced5e..26acddaa7b53fd 100644 --- a/src/mono/browser/runtime/pthreads/worker-thread.ts +++ b/src/mono/browser/runtime/pthreads/worker-thread.ts @@ -6,7 +6,7 @@ import WasmEnableThreads from "consts:wasmEnableThreads"; import { ENVIRONMENT_IS_PTHREAD, Module, loaderHelpers, mono_assert, runtimeHelpers } from "../globals"; -import { PThreadSelf, monoThreadInfo, mono_wasm_pthread_ptr, postMessageToMain, update_thread_info } from "./shared"; +import { PThreadSelf, monoThreadInfo, postMessageToMain, update_thread_info } from "./shared"; import { PThreadLibrary, MonoThreadMessage, PThreadInfo, PThreadPtr, WorkerToMainMessageType } from "../types/internal"; import { makeWorkerThreadEvent, @@ -14,11 +14,12 @@ import { dotnetPthreadAttached, WorkerThreadEventTarget } from "./worker-events"; -import { postRunWorker, preRunWorker } from "../startup"; +import { postRunWorker } from "../startup"; import { mono_log_debug, mono_log_error } from "../logging"; import { CharPtr } from "../types/emscripten"; import { utf8ToString } from "../strings"; import { forceThreadMemoryViewRefresh } from "../memory"; +import { threads_c_functions as tcwraps } from "../cwraps"; // re-export some of the events types export { @@ -80,8 +81,7 @@ export function mono_wasm_pthread_on_pthread_created (): void { if (!WasmEnableThreads) return; try { forceThreadMemoryViewRefresh(); - const pthread_id = mono_wasm_pthread_ptr(); - mono_assert(pthread_id == monoThreadInfo.pthreadId, `needs to match (mono_wasm_pthread_ptr ${pthread_id}, threadId from thread info ${monoThreadInfo.pthreadId})`); + monoThreadInfo.pthreadId = tcwraps.pthread_self(); monoThreadInfo.reuseCount++; monoThreadInfo.updateCount++; @@ -128,7 +128,6 @@ export function mono_wasm_pthread_on_pthread_registered (pthread_id: PThreadPtr) monoCmd: WorkerToMainMessageType.monoRegistered, info: monoThreadInfo, }); - preRunWorker(); } catch (err) { mono_log_error("mono_wasm_pthread_on_pthread_registered () failed", err); loaderHelpers.mono_exit(1, err); diff --git a/src/mono/browser/runtime/startup.ts b/src/mono/browser/runtime/startup.ts index 25093725766169..37fa068809cbd2 100644 --- a/src/mono/browser/runtime/startup.ts +++ b/src/mono/browser/runtime/startup.ts @@ -25,7 +25,7 @@ import { mono_log_debug, mono_log_error, mono_log_info, mono_log_warn } from "./ // threads import { populateEmscriptenPool, mono_wasm_init_threads } from "./pthreads"; import { currentWorkerThreadEvents, dotnetPthreadCreated, initWorkerThreadEvents, monoThreadInfo } from "./pthreads"; -import { mono_wasm_pthread_ptr, update_thread_info } from "./pthreads"; +import { update_thread_info } from "./pthreads"; import { jiterpreter_allocate_tables } from "./jiterpreter-support"; import { localHeapViewU8, malloc, setU32, fixupPointer } from "./memory"; import { assertNoProxies } from "./gc-handles"; @@ -146,6 +146,7 @@ async function instantiateWasmWorker ( const instance = new WebAssembly.Instance(Module.wasmModule!, imports); successCallback(instance, undefined); Module.wasmModule = null; + preRunWorker(); } @@ -473,7 +474,7 @@ export async function start_runtime () { monoThreadInfo.isAttached = true; monoThreadInfo.isRunning = true; monoThreadInfo.isRegistered = true; - runtimeHelpers.currentThreadTID = monoThreadInfo.pthreadId = runtimeHelpers.managedThreadTID = mono_wasm_pthread_ptr(); + runtimeHelpers.currentThreadTID = monoThreadInfo.pthreadId = runtimeHelpers.managedThreadTID = tcwraps.pthread_self(); update_thread_info(); runtimeHelpers.isManagedRunningOnCurrentThread = true; } diff --git a/src/mono/browser/runtime/types/internal.ts b/src/mono/browser/runtime/types/internal.ts index 5412d6292ec6ad..e4fd0e3bd4607a 100644 --- a/src/mono/browser/runtime/types/internal.ts +++ b/src/mono/browser/runtime/types/internal.ts @@ -436,7 +436,9 @@ export declare interface EmscriptenModuleInternal { mainScriptUrlOrBlob?: string; ENVIRONMENT_IS_PTHREAD?: boolean; FS: any; - wasmModule: WebAssembly.Instance | null; + wasmModule: WebAssembly.Module | null; + wasmMemory: WebAssembly.Memory | null; + handlers: any; wasmExports: any; getWasmTableEntry(index: number): any; removeRunDependency(id: string): void; diff --git a/src/mono/sample/wasm/browser-threads/Wasm.Browser.Threads.Sample.csproj b/src/mono/sample/wasm/browser-threads/Wasm.Browser.Threads.Sample.csproj index 95f97960be068c..1858d246b3dd50 100644 --- a/src/mono/sample/wasm/browser-threads/Wasm.Browser.Threads.Sample.csproj +++ b/src/mono/sample/wasm/browser-threads/Wasm.Browser.Threads.Sample.csproj @@ -1,6 +1,10 @@ true + true + false + false + false diff --git a/src/mono/sample/wasm/browser-threads/wwwroot/main.js b/src/mono/sample/wasm/browser-threads/wwwroot/main.js index 9befe7d600c693..5653a68c15a1cf 100644 --- a/src/mono/sample/wasm/browser-threads/wwwroot/main.js +++ b/src/mono/sample/wasm/browser-threads/wwwroot/main.js @@ -19,6 +19,7 @@ try { exitOnUnhandledError: true, logExitCode: true, jsThreadBlockingMode: "WarnWhenBlockingWait", + pthreadPoolInitialSize: 15, }) .create(); From dff06ef6df4d9cb0a3f3dd5bb712ee6abfd28207 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Wed, 17 Jun 2026 12:02:38 +0200 Subject: [PATCH 02/15] fix MT --- src/mono/browser/browser.proj | 1 + src/mono/browser/runtime/loader/assets.ts | 3 ++ src/mono/browser/runtime/loader/run.ts | 39 ++++++++++++------- .../browser/runtime/pthreads/ui-thread.ts | 7 ++++ .../browser/runtime/pthreads/worker-thread.ts | 2 + src/mono/browser/runtime/startup.ts | 1 - src/mono/browser/runtime/types/internal.ts | 2 + .../Wasm.Advanced.Sample.csproj | 2 + 8 files changed, 43 insertions(+), 14 deletions(-) diff --git a/src/mono/browser/browser.proj b/src/mono/browser/browser.proj index b9f72ca324b0fd..93a3f0bd930ccf 100644 --- a/src/mono/browser/browser.proj +++ b/src/mono/browser/browser.proj @@ -142,6 +142,7 @@ + diff --git a/src/mono/browser/runtime/loader/assets.ts b/src/mono/browser/runtime/loader/assets.ts index 76b3291d7de9f9..b23d66a3eac962 100644 --- a/src/mono/browser/runtime/loader/assets.ts +++ b/src/mono/browser/runtime/loader/assets.ts @@ -793,6 +793,9 @@ export function preloadWorkers () { threadPrefix: worker_empty_prefix, threadName: "emscripten-pool", } as any; + worker.queue = []; + worker.handler = (ev) => worker.queue!.push(ev); + worker.addEventListener!("message", worker.handler); loadingWorkers.push(worker as any); } loaderHelpers.loadingWorkers.promise_control.resolve(loadingWorkers); diff --git a/src/mono/browser/runtime/loader/run.ts b/src/mono/browser/runtime/loader/run.ts index ba803cc29d87de..ce9cca3ed43c28 100644 --- a/src/mono/browser/runtime/loader/run.ts +++ b/src/mono/browser/runtime/loader/run.ts @@ -324,9 +324,7 @@ export async function createEmscripten (moduleFactory: DotnetModuleConfig | ((ap registerEmscriptenExitHandlers(); - return emscriptenModule.ENVIRONMENT_IS_PTHREAD - ? createEmscriptenWorker() - : createEmscriptenMain(); + return createEmscriptenMain(); } let jsModuleRuntimePromise: Promise; @@ -451,19 +449,34 @@ async function createEmscriptenWorker (): Promise { await loaderHelpers.afterConfigLoaded.promise; prepareAssetsWorker(); - - setTimeout(async () => { - try { - // load subset which is on JS heap rather than in WASM linear memory - await mono_download_assets(); - } catch (err) { - mono_exit(1, err); - } - }, 0); - const promises = importModules(); const es6Modules = await Promise.all(promises); await initializeModules(es6Modules as any); + if (loaderHelpers.config.exitOnUnhandledError) { + installUnhandledErrorHandler(); + } + registerEmscriptenExitHandlers(); + if (ENVIRONMENT_IS_WEB && loaderHelpers.config.forwardConsole && typeof globalThis.WebSocket != "undefined") { + setup_proxy_console("main", globalThis.console, globalThis.location.origin); + } + + await detect_features_and_polyfill(emscriptenModule); + + await mono_download_assets(); + + self.dispatchEvent(new MessageEvent("message", { + data: { + cmd: "load", + handlers: emscriptenModule.handlers, + wasmMemory: emscriptenModule.wasmMemory, + wasmModule: emscriptenModule.wasmModule + } + })); + return emscriptenModule; } + +if (ENVIRONMENT_IS_WORKER) { + createEmscriptenWorker(); +} diff --git a/src/mono/browser/runtime/pthreads/ui-thread.ts b/src/mono/browser/runtime/pthreads/ui-thread.ts index b15de7dd887230..24151eb9c86a79 100644 --- a/src/mono/browser/runtime/pthreads/ui-thread.ts +++ b/src/mono/browser/runtime/pthreads/ui-thread.ts @@ -213,6 +213,10 @@ export function replaceEmscriptenPThreadUI (modulePThread: PThreadLibrary): void modulePThread.loadWasmModuleToWorker = async (worker: PThreadWorker): Promise => { const wasmModule = await loaderHelpers.wasmCompilePromise.promise; + for (const ev in worker.queue) { + monoWorkerMessageHandler(worker, wasmModule, worker.queue[ev]); + } + worker.queue.length = 0; worker.addEventListener("message", (ev) => monoWorkerMessageHandler(worker, wasmModule, ev)); const afterLoaded = originalLoadWasmModuleToWorker(worker); afterLoaded.then(() => { @@ -304,6 +308,9 @@ function allocateUnusedWorker (): PThreadWorker { threadPrefix: worker_empty_prefix, threadName: "emscripten-pool", }; + worker.queue = []; + worker.handler = (ev) => worker.queue!.push(ev); + worker.addEventListener!("message", worker.handler); return worker; } diff --git a/src/mono/browser/runtime/pthreads/worker-thread.ts b/src/mono/browser/runtime/pthreads/worker-thread.ts index 26acddaa7b53fd..32cd178db3db83 100644 --- a/src/mono/browser/runtime/pthreads/worker-thread.ts +++ b/src/mono/browser/runtime/pthreads/worker-thread.ts @@ -20,6 +20,7 @@ import { CharPtr } from "../types/emscripten"; import { utf8ToString } from "../strings"; import { forceThreadMemoryViewRefresh } from "../memory"; import { threads_c_functions as tcwraps } from "../cwraps"; +import { jiterpreter_allocate_tables } from "../jiterpreter-support"; // re-export some of the events types export { @@ -87,6 +88,7 @@ export function mono_wasm_pthread_on_pthread_created (): void { monoThreadInfo.updateCount++; monoThreadInfo.threadName = "pthread-assigned"; update_thread_info(); + jiterpreter_allocate_tables(); // don't do this callback for the main thread if (!ENVIRONMENT_IS_PTHREAD) return; diff --git a/src/mono/browser/runtime/startup.ts b/src/mono/browser/runtime/startup.ts index 37fa068809cbd2..3d7116f8df0cab 100644 --- a/src/mono/browser/runtime/startup.ts +++ b/src/mono/browser/runtime/startup.ts @@ -158,7 +158,6 @@ export function preRunWorker () { mono_log_debug("preRunWorker"); init_c_exports(); cwraps_internal(INTERNAL); - jiterpreter_allocate_tables(); // this will return quickly if already allocated runtimeHelpers.nativeExit = nativeExit; runtimeHelpers.nativeAbort = nativeAbort; runtimeHelpers.runtimeReady = true; diff --git a/src/mono/browser/runtime/types/internal.ts b/src/mono/browser/runtime/types/internal.ts index e4fd0e3bd4607a..48f989867fbc81 100644 --- a/src/mono/browser/runtime/types/internal.ts +++ b/src/mono/browser/runtime/types/internal.ts @@ -538,6 +538,8 @@ export interface PThreadWorker extends Worker { // this info is updated via async messages from the worker, it could be stale info: PThreadInfo; thread?: Thread; + queue: MessageEvent[]; + handler: ((ev: MessageEvent) => void) | null; } export interface PThreadInfo { diff --git a/src/mono/sample/wasm/browser-advanced/Wasm.Advanced.Sample.csproj b/src/mono/sample/wasm/browser-advanced/Wasm.Advanced.Sample.csproj index 815953ebc35a40..365a61629523a8 100644 --- a/src/mono/sample/wasm/browser-advanced/Wasm.Advanced.Sample.csproj +++ b/src/mono/sample/wasm/browser-advanced/Wasm.Advanced.Sample.csproj @@ -8,6 +8,8 @@ true true web,worker + false + <_WasmAllowAOTDebug>true -s USE_CLOSURE_COMPILER=1 -s LEGACY_GL_EMULATION=1 -lGL -lSDL -lidbfs.js From 0d1e5b3e8de0f205f97fc0b626cb3b79c2c1798f Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Wed, 17 Jun 2026 12:02:50 +0200 Subject: [PATCH 03/15] disable MT --- .../templates/browser-wasm-build-tests.yml | 32 ++++++------- eng/pipelines/common/xplat-setup.yml | 4 +- .../runtime-extra-platforms-wasm.yml | 48 +++++++++---------- eng/pipelines/runtime.yml | 20 ++++---- 4 files changed, 52 insertions(+), 52 deletions(-) diff --git a/eng/pipelines/common/templates/browser-wasm-build-tests.yml b/eng/pipelines/common/templates/browser-wasm-build-tests.yml index 39fca50137411f..278b12ff72ceba 100644 --- a/eng/pipelines/common/templates/browser-wasm-build-tests.yml +++ b/eng/pipelines/common/templates/browser-wasm-build-tests.yml @@ -42,10 +42,10 @@ jobs: jobParameters: dependsOn: - ${{ if eq(platform, 'browser_wasm') }}: - - build_browser_wasm_linux_Release_MultiThreaded_BuildOnly + # - build_browser_wasm_linux_Release_MultiThreaded_BuildOnly - build_browser_wasm_linux_Release_SingleThreaded_BuildOnly - ${{ if eq(platform, 'browser_wasm_win') }}: - - build_browser_wasm_windows_Release_MultiThreaded_BuildOnly + # - build_browser_wasm_windows_Release_MultiThreaded_BuildOnly - build_browser_wasm_windows_Release_SingleThreaded_BuildOnly isExtraPlatforms: ${{ parameters.isExtraPlatformsBuild }} testGroup: innerloop @@ -69,21 +69,21 @@ jobs: CleanTargetFolder: false # Download for multi-threaded - - task: DownloadBuildArtifacts@0 - displayName: Download built nugets for multi-threaded runtime - inputs: - buildType: current - artifactName: BuildArtifacts_browser_wasm_$(_hostedOs)_Release_MultiThreaded_BuildOnly - downloadType: single - downloadPath: '$(Build.SourcesDirectory)/artifacts' + # - task: DownloadBuildArtifacts@0 + # displayName: Download built nugets for multi-threaded runtime + # inputs: + # buildType: current + # artifactName: BuildArtifacts_browser_wasm_$(_hostedOs)_Release_MultiThreaded_BuildOnly + # downloadType: single + # downloadPath: '$(Build.SourcesDirectory)/artifacts' - - task: CopyFiles@2 - displayName: Copy multithreading runtime pack - inputs: - SourceFolder: '$(Build.SourcesDirectory)/artifacts/BuildArtifacts_browser_wasm_$(_hostedOs)_Release_MultiThreaded_BuildOnly' - Contents: packages/$(_BuildConfig)/Shipping/Microsoft.NETCore.App.Runtime.Mono.multithread.* - TargetFolder: '$(Build.SourcesDirectory)/artifacts' - CleanTargetFolder: false + # - task: CopyFiles@2 + # displayName: Copy multithreading runtime pack + # inputs: + # SourceFolder: '$(Build.SourcesDirectory)/artifacts/BuildArtifacts_browser_wasm_$(_hostedOs)_Release_MultiThreaded_BuildOnly' + # Contents: packages/$(_BuildConfig)/Shipping/Microsoft.NETCore.App.Runtime.Mono.multithread.* + # TargetFolder: '$(Build.SourcesDirectory)/artifacts' + # CleanTargetFolder: false # Download WBT - task: DownloadBuildArtifacts@0 diff --git a/eng/pipelines/common/xplat-setup.yml b/eng/pipelines/common/xplat-setup.yml index 1f28b57b49f943..6f4ab2d00f874d 100644 --- a/eng/pipelines/common/xplat-setup.yml +++ b/eng/pipelines/common/xplat-setup.yml @@ -139,8 +139,8 @@ jobs: # needed for Wasm.Build.Tests - name: wasmSingleThreadedBuildOnlyNeededOnDefaultPipeline value: $[ variables['shouldRunWasmBuildTestsOnDefaultPipeline'] ] - - name: wasmMultiThreadedBuildOnlyNeededOnDefaultPipeline - value: $[ variables['shouldRunWasmBuildTestsOnDefaultPipeline'] ] + # - name: wasmMultiThreadedBuildOnlyNeededOnDefaultPipeline + # value: $[ variables['shouldRunWasmBuildTestsOnDefaultPipeline'] ] - ${{ each variable in parameters.variables }}: - ${{ variable }} diff --git a/eng/pipelines/extra-platforms/runtime-extra-platforms-wasm.yml b/eng/pipelines/extra-platforms/runtime-extra-platforms-wasm.yml index 1fd298c4ecead8..87d6ace0d820a2 100644 --- a/eng/pipelines/extra-platforms/runtime-extra-platforms-wasm.yml +++ b/eng/pipelines/extra-platforms/runtime-extra-platforms-wasm.yml @@ -77,21 +77,21 @@ jobs: - WasmTestOnChrome # Library tests with full threading - - template: /eng/pipelines/common/templates/wasm-library-tests.yml - parameters: - platforms: - - browser_wasm - #- browser_wasm_win - nameSuffix: _Threading - extraBuildArgs: /p:WasmEnableThreads=true /p:AotHostArchitecture=x64 /p:AotHostOS=$(_hostedOS) - extraHelixArguments: /p:WasmEnableThreads=true - shouldContinueOnError: true - isExtraPlatformsBuild: ${{ parameters.isExtraPlatformsBuild }} - isWasmOnlyBuild: ${{ parameters.isWasmOnlyBuild }} - alwaysRun: ${{ parameters.isWasmOnlyBuild }} - shouldRunSmokeOnly: onLibrariesAndIllinkChanges - scenarios: - - WasmTestOnChrome + # - template: /eng/pipelines/common/templates/wasm-library-tests.yml + # parameters: + # platforms: + # - browser_wasm + # #- browser_wasm_win + # nameSuffix: _Threading + # extraBuildArgs: /p:WasmEnableThreads=true /p:AotHostArchitecture=x64 /p:AotHostOS=$(_hostedOS) + # extraHelixArguments: /p:WasmEnableThreads=true + # shouldContinueOnError: true + # isExtraPlatformsBuild: ${{ parameters.isExtraPlatformsBuild }} + # isWasmOnlyBuild: ${{ parameters.isWasmOnlyBuild }} + # alwaysRun: ${{ parameters.isWasmOnlyBuild }} + # shouldRunSmokeOnly: onLibrariesAndIllinkChanges + # scenarios: + # - WasmTestOnChrome # EAT Library tests - only run on linux - template: /eng/pipelines/common/templates/wasm-library-aot-tests.yml @@ -177,15 +177,15 @@ jobs: publishArtifactsForWorkload: true publishWBT: true - - template: /eng/pipelines/common/templates/wasm-build-only.yml - parameters: - platforms: - - browser_wasm - - browser_wasm_win - nameSuffix: MultiThreaded - extraBuildArgs: /p:WasmEnableThreads=true /p:AotHostArchitecture=x64 /p:AotHostOS=$(_hostedOS) - publishArtifactsForWorkload: true - publishWBT: false + # - template: /eng/pipelines/common/templates/wasm-build-only.yml + # parameters: + # platforms: + # - browser_wasm + # - browser_wasm_win + # nameSuffix: MultiThreaded + # extraBuildArgs: /p:WasmEnableThreads=true /p:AotHostArchitecture=x64 /p:AotHostOS=$(_hostedOS) + # publishArtifactsForWorkload: true + # publishWBT: false # Browser Wasm.Build.Tests - template: /eng/pipelines/common/templates/browser-wasm-build-tests.yml diff --git a/eng/pipelines/runtime.yml b/eng/pipelines/runtime.yml index 18afad1723cead..dd6ed7996e62fa 100644 --- a/eng/pipelines/runtime.yml +++ b/eng/pipelines/runtime.yml @@ -1040,16 +1040,16 @@ extends: publishArtifactsForWorkload: true publishWBT: true - - template: /eng/pipelines/common/templates/wasm-build-only.yml - parameters: - platforms: - - browser_wasm - - browser_wasm_win - condition: or(eq(variables.isRollingBuild, true), eq(variables.wasmSingleThreadedBuildOnlyNeededOnDefaultPipeline, true)) - nameSuffix: MultiThreaded - extraBuildArgs: /p:WasmEnableThreads=true /p:AotHostArchitecture=x64 /p:AotHostOS=$(_hostedOS) - publishArtifactsForWorkload: true - publishWBT: false + # - template: /eng/pipelines/common/templates/wasm-build-only.yml + # parameters: + # platforms: + # - browser_wasm + # - browser_wasm_win + # condition: or(eq(variables.isRollingBuild, true), eq(variables.wasmSingleThreadedBuildOnlyNeededOnDefaultPipeline, true)) + # nameSuffix: MultiThreaded + # extraBuildArgs: /p:WasmEnableThreads=true /p:AotHostArchitecture=x64 /p:AotHostOS=$(_hostedOS) + # publishArtifactsForWorkload: true + # publishWBT: false # Browser Wasm.Build.Tests - template: /eng/pipelines/common/templates/browser-wasm-build-tests.yml From 631164763b4672f9450c9eaf48bf572c646df0ba Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Wed, 17 Jun 2026 14:44:42 +0200 Subject: [PATCH 04/15] fix --- src/mono/browser/runtime/loader/run.ts | 27 +++----------------------- 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/src/mono/browser/runtime/loader/run.ts b/src/mono/browser/runtime/loader/run.ts index ce9cca3ed43c28..b11bfddd8716e6 100644 --- a/src/mono/browser/runtime/loader/run.ts +++ b/src/mono/browser/runtime/loader/run.ts @@ -324,7 +324,9 @@ export async function createEmscripten (moduleFactory: DotnetModuleConfig | ((ap registerEmscriptenExitHandlers(); - return createEmscriptenMain(); + return emscriptenModule.ENVIRONMENT_IS_PTHREAD + ? createEmscriptenWorker() + : createEmscriptenMain(); } let jsModuleRuntimePromise: Promise; @@ -453,30 +455,7 @@ async function createEmscriptenWorker (): Promise { const es6Modules = await Promise.all(promises); await initializeModules(es6Modules as any); - if (loaderHelpers.config.exitOnUnhandledError) { - installUnhandledErrorHandler(); - } - registerEmscriptenExitHandlers(); - if (ENVIRONMENT_IS_WEB && loaderHelpers.config.forwardConsole && typeof globalThis.WebSocket != "undefined") { - setup_proxy_console("main", globalThis.console, globalThis.location.origin); - } - - await detect_features_and_polyfill(emscriptenModule); - await mono_download_assets(); - self.dispatchEvent(new MessageEvent("message", { - data: { - cmd: "load", - handlers: emscriptenModule.handlers, - wasmMemory: emscriptenModule.wasmMemory, - wasmModule: emscriptenModule.wasmModule - } - })); - return emscriptenModule; } - -if (ENVIRONMENT_IS_WORKER) { - createEmscriptenWorker(); -} From 73e76a6911f76826c3b5e9d1d7b5ca3e65703120 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Wed, 17 Jun 2026 17:03:31 +0200 Subject: [PATCH 05/15] disable broken stuff --- eng/testing/tests.browser.targets | 10 ++++++++-- src/libraries/tests.proj | 4 +--- .../Microsoft.NET.Sdk.WebAssembly.Browser.targets | 3 +++ 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/eng/testing/tests.browser.targets b/eng/testing/tests.browser.targets index cefc5b3b52caae..0db156f0ec40be 100644 --- a/eng/testing/tests.browser.targets +++ b/eng/testing/tests.browser.targets @@ -127,10 +127,14 @@ <_AppArgs Condition="'$(WasmTestAppArgs)' != ''">$(_AppArgs) $(WasmTestAppArgs) $(WasmXHarnessMonoArgs) --setenv=XHARNESS_LOG_TEST_START=true - - $(WasmXHarnessMonoArgs) --setenv=IsBrowserThreadingSupported=true 8 + + + + + + <_XHarnessArgs Condition="'$(OS)' != 'Windows_NT'">wasm $XHARNESS_COMMAND --app=. --output-directory=$XHARNESS_OUT @@ -148,7 +152,9 @@ <_XHarnessArgs Condition="'$(WasmXHarnessVerbosity)' != ''" >$(_XHarnessArgs) --verbosity=$(WasmXHarnessVerbosity) <_XHarnessArgs Condition="'$(WasmXHarnessArgsCli)' != ''" >$(_XHarnessArgs) $(WasmXHarnessArgsCli) + <_AppArgs Condition="'$(WasmXHarnessMaxParallelThreads)' != ''">$(_AppArgs) -parallelThreads $(WasmXHarnessMaxParallelThreads) $HARNESS_RUNNER $(_XHarnessArgs) %24XHARNESS_ARGS %24WasmXHarnessArgs -- $(WasmXHarnessMonoArgs) %24WasmXHarnessMonoArgs $(_AppArgs) %24WasmTestAppArgs diff --git a/src/libraries/tests.proj b/src/libraries/tests.proj index 7d669d21533042..3783247c6871ff 100644 --- a/src/libraries/tests.proj +++ b/src/libraries/tests.proj @@ -582,9 +582,7 @@ - - - + diff --git a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets index ab8d6606aba35e..7e2a6bdb54f174 100644 --- a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets +++ b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets @@ -248,6 +248,9 @@ Copyright (c) .NET Foundation. All rights reserved. + + + From fa63024b192aa19645d5197df52a3de3786390ce Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Wed, 17 Jun 2026 17:57:48 +0200 Subject: [PATCH 06/15] keep MT pipelines --- .../templates/browser-wasm-build-tests.yml | 32 ++++++------- eng/pipelines/common/xplat-setup.yml | 4 +- .../runtime-extra-platforms-wasm.yml | 48 +++++++++---------- eng/pipelines/runtime.yml | 20 ++++---- 4 files changed, 52 insertions(+), 52 deletions(-) diff --git a/eng/pipelines/common/templates/browser-wasm-build-tests.yml b/eng/pipelines/common/templates/browser-wasm-build-tests.yml index 278b12ff72ceba..39fca50137411f 100644 --- a/eng/pipelines/common/templates/browser-wasm-build-tests.yml +++ b/eng/pipelines/common/templates/browser-wasm-build-tests.yml @@ -42,10 +42,10 @@ jobs: jobParameters: dependsOn: - ${{ if eq(platform, 'browser_wasm') }}: - # - build_browser_wasm_linux_Release_MultiThreaded_BuildOnly + - build_browser_wasm_linux_Release_MultiThreaded_BuildOnly - build_browser_wasm_linux_Release_SingleThreaded_BuildOnly - ${{ if eq(platform, 'browser_wasm_win') }}: - # - build_browser_wasm_windows_Release_MultiThreaded_BuildOnly + - build_browser_wasm_windows_Release_MultiThreaded_BuildOnly - build_browser_wasm_windows_Release_SingleThreaded_BuildOnly isExtraPlatforms: ${{ parameters.isExtraPlatformsBuild }} testGroup: innerloop @@ -69,21 +69,21 @@ jobs: CleanTargetFolder: false # Download for multi-threaded - # - task: DownloadBuildArtifacts@0 - # displayName: Download built nugets for multi-threaded runtime - # inputs: - # buildType: current - # artifactName: BuildArtifacts_browser_wasm_$(_hostedOs)_Release_MultiThreaded_BuildOnly - # downloadType: single - # downloadPath: '$(Build.SourcesDirectory)/artifacts' + - task: DownloadBuildArtifacts@0 + displayName: Download built nugets for multi-threaded runtime + inputs: + buildType: current + artifactName: BuildArtifacts_browser_wasm_$(_hostedOs)_Release_MultiThreaded_BuildOnly + downloadType: single + downloadPath: '$(Build.SourcesDirectory)/artifacts' - # - task: CopyFiles@2 - # displayName: Copy multithreading runtime pack - # inputs: - # SourceFolder: '$(Build.SourcesDirectory)/artifacts/BuildArtifacts_browser_wasm_$(_hostedOs)_Release_MultiThreaded_BuildOnly' - # Contents: packages/$(_BuildConfig)/Shipping/Microsoft.NETCore.App.Runtime.Mono.multithread.* - # TargetFolder: '$(Build.SourcesDirectory)/artifacts' - # CleanTargetFolder: false + - task: CopyFiles@2 + displayName: Copy multithreading runtime pack + inputs: + SourceFolder: '$(Build.SourcesDirectory)/artifacts/BuildArtifacts_browser_wasm_$(_hostedOs)_Release_MultiThreaded_BuildOnly' + Contents: packages/$(_BuildConfig)/Shipping/Microsoft.NETCore.App.Runtime.Mono.multithread.* + TargetFolder: '$(Build.SourcesDirectory)/artifacts' + CleanTargetFolder: false # Download WBT - task: DownloadBuildArtifacts@0 diff --git a/eng/pipelines/common/xplat-setup.yml b/eng/pipelines/common/xplat-setup.yml index 6f4ab2d00f874d..1f28b57b49f943 100644 --- a/eng/pipelines/common/xplat-setup.yml +++ b/eng/pipelines/common/xplat-setup.yml @@ -139,8 +139,8 @@ jobs: # needed for Wasm.Build.Tests - name: wasmSingleThreadedBuildOnlyNeededOnDefaultPipeline value: $[ variables['shouldRunWasmBuildTestsOnDefaultPipeline'] ] - # - name: wasmMultiThreadedBuildOnlyNeededOnDefaultPipeline - # value: $[ variables['shouldRunWasmBuildTestsOnDefaultPipeline'] ] + - name: wasmMultiThreadedBuildOnlyNeededOnDefaultPipeline + value: $[ variables['shouldRunWasmBuildTestsOnDefaultPipeline'] ] - ${{ each variable in parameters.variables }}: - ${{ variable }} diff --git a/eng/pipelines/extra-platforms/runtime-extra-platforms-wasm.yml b/eng/pipelines/extra-platforms/runtime-extra-platforms-wasm.yml index 87d6ace0d820a2..1fd298c4ecead8 100644 --- a/eng/pipelines/extra-platforms/runtime-extra-platforms-wasm.yml +++ b/eng/pipelines/extra-platforms/runtime-extra-platforms-wasm.yml @@ -77,21 +77,21 @@ jobs: - WasmTestOnChrome # Library tests with full threading - # - template: /eng/pipelines/common/templates/wasm-library-tests.yml - # parameters: - # platforms: - # - browser_wasm - # #- browser_wasm_win - # nameSuffix: _Threading - # extraBuildArgs: /p:WasmEnableThreads=true /p:AotHostArchitecture=x64 /p:AotHostOS=$(_hostedOS) - # extraHelixArguments: /p:WasmEnableThreads=true - # shouldContinueOnError: true - # isExtraPlatformsBuild: ${{ parameters.isExtraPlatformsBuild }} - # isWasmOnlyBuild: ${{ parameters.isWasmOnlyBuild }} - # alwaysRun: ${{ parameters.isWasmOnlyBuild }} - # shouldRunSmokeOnly: onLibrariesAndIllinkChanges - # scenarios: - # - WasmTestOnChrome + - template: /eng/pipelines/common/templates/wasm-library-tests.yml + parameters: + platforms: + - browser_wasm + #- browser_wasm_win + nameSuffix: _Threading + extraBuildArgs: /p:WasmEnableThreads=true /p:AotHostArchitecture=x64 /p:AotHostOS=$(_hostedOS) + extraHelixArguments: /p:WasmEnableThreads=true + shouldContinueOnError: true + isExtraPlatformsBuild: ${{ parameters.isExtraPlatformsBuild }} + isWasmOnlyBuild: ${{ parameters.isWasmOnlyBuild }} + alwaysRun: ${{ parameters.isWasmOnlyBuild }} + shouldRunSmokeOnly: onLibrariesAndIllinkChanges + scenarios: + - WasmTestOnChrome # EAT Library tests - only run on linux - template: /eng/pipelines/common/templates/wasm-library-aot-tests.yml @@ -177,15 +177,15 @@ jobs: publishArtifactsForWorkload: true publishWBT: true - # - template: /eng/pipelines/common/templates/wasm-build-only.yml - # parameters: - # platforms: - # - browser_wasm - # - browser_wasm_win - # nameSuffix: MultiThreaded - # extraBuildArgs: /p:WasmEnableThreads=true /p:AotHostArchitecture=x64 /p:AotHostOS=$(_hostedOS) - # publishArtifactsForWorkload: true - # publishWBT: false + - template: /eng/pipelines/common/templates/wasm-build-only.yml + parameters: + platforms: + - browser_wasm + - browser_wasm_win + nameSuffix: MultiThreaded + extraBuildArgs: /p:WasmEnableThreads=true /p:AotHostArchitecture=x64 /p:AotHostOS=$(_hostedOS) + publishArtifactsForWorkload: true + publishWBT: false # Browser Wasm.Build.Tests - template: /eng/pipelines/common/templates/browser-wasm-build-tests.yml diff --git a/eng/pipelines/runtime.yml b/eng/pipelines/runtime.yml index dd6ed7996e62fa..18afad1723cead 100644 --- a/eng/pipelines/runtime.yml +++ b/eng/pipelines/runtime.yml @@ -1040,16 +1040,16 @@ extends: publishArtifactsForWorkload: true publishWBT: true - # - template: /eng/pipelines/common/templates/wasm-build-only.yml - # parameters: - # platforms: - # - browser_wasm - # - browser_wasm_win - # condition: or(eq(variables.isRollingBuild, true), eq(variables.wasmSingleThreadedBuildOnlyNeededOnDefaultPipeline, true)) - # nameSuffix: MultiThreaded - # extraBuildArgs: /p:WasmEnableThreads=true /p:AotHostArchitecture=x64 /p:AotHostOS=$(_hostedOS) - # publishArtifactsForWorkload: true - # publishWBT: false + - template: /eng/pipelines/common/templates/wasm-build-only.yml + parameters: + platforms: + - browser_wasm + - browser_wasm_win + condition: or(eq(variables.isRollingBuild, true), eq(variables.wasmSingleThreadedBuildOnlyNeededOnDefaultPipeline, true)) + nameSuffix: MultiThreaded + extraBuildArgs: /p:WasmEnableThreads=true /p:AotHostArchitecture=x64 /p:AotHostOS=$(_hostedOS) + publishArtifactsForWorkload: true + publishWBT: false # Browser Wasm.Build.Tests - template: /eng/pipelines/common/templates/browser-wasm-build-tests.yml From b59d6ac5cd0f6ef8470d334db0201eabd2be451e Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Wed, 17 Jun 2026 18:16:00 +0200 Subject: [PATCH 07/15] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/mono/browser/runtime/pthreads/ui-thread.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/mono/browser/runtime/pthreads/ui-thread.ts b/src/mono/browser/runtime/pthreads/ui-thread.ts index 24151eb9c86a79..3f7b8d2765698c 100644 --- a/src/mono/browser/runtime/pthreads/ui-thread.ts +++ b/src/mono/browser/runtime/pthreads/ui-thread.ts @@ -213,11 +213,20 @@ export function replaceEmscriptenPThreadUI (modulePThread: PThreadLibrary): void modulePThread.loadWasmModuleToWorker = async (worker: PThreadWorker): Promise => { const wasmModule = await loaderHelpers.wasmCompilePromise.promise; - for (const ev in worker.queue) { - monoWorkerMessageHandler(worker, wasmModule, worker.queue[ev]); + + // Stop queueing once we can process messages synchronously. + if (worker.handler) { + worker.removeEventListener("message", worker.handler); + } + + for (const queuedEvent of worker.queue) { + monoWorkerMessageHandler(worker, wasmModule, queuedEvent); } + worker.queue.length = 0; - worker.addEventListener("message", (ev) => monoWorkerMessageHandler(worker, wasmModule, ev)); + worker.handler = (ev) => monoWorkerMessageHandler(worker, wasmModule, ev); + worker.addEventListener("message", worker.handler); + const afterLoaded = originalLoadWasmModuleToWorker(worker); afterLoaded.then(() => { worker.info.isLoaded = true; From 06673e39dc544d52454ccb4d997068817948f8be Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Thu, 18 Jun 2026 09:03:11 +0200 Subject: [PATCH 08/15] feedback --- src/mono/browser/runtime/loader/worker.ts | 2 +- .../browser/runtime/pthreads/ui-thread.ts | 38 ++++++++++--------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/mono/browser/runtime/loader/worker.ts b/src/mono/browser/runtime/loader/worker.ts index 8d7b9f6f87a3c4..05bc8655ff931c 100644 --- a/src/mono/browser/runtime/loader/worker.ts +++ b/src/mono/browser/runtime/loader/worker.ts @@ -28,7 +28,7 @@ export function setupPreloadChannelToMainThread () { let workerMonoConfigReceived = false; // called when the main thread sends us the mono config -async function onMonoConfigReceived (data: any): Promise { +function onMonoConfigReceived (data: any): void { const config = JSON.parse(data.config) as MonoConfigInternal; const monoThreadInfo = JSON.parse(data.monoThreadInfo) as PThreadInfo; emscriptenModule.config = config; diff --git a/src/mono/browser/runtime/pthreads/ui-thread.ts b/src/mono/browser/runtime/pthreads/ui-thread.ts index 3f7b8d2765698c..6027a041d9d987 100644 --- a/src/mono/browser/runtime/pthreads/ui-thread.ts +++ b/src/mono/browser/runtime/pthreads/ui-thread.ts @@ -75,26 +75,28 @@ function monoWorkerMessageHandler (worker: PThreadWorker, wasmModule: WebAssembl let thread: Thread; pthreadId = message.info?.pthreadId ?? 0; worker.info = Object.assign({}, worker.info, message.info); - const wasmMemory = runtimeHelpers.getMemory(); - const handlers = []; - const knownHandlers = ["onExit", "onAbort", "print", "printErr"]; - for (const handler of knownHandlers) { - if (Object.prototype.propertyIsEnumerable.call(Module, handler)) { - handlers.push(handler); - } - } switch (message.monoCmd) { case WorkerToMainMessageType.preload: - // this one shot port from setupPreloadChannelToMainThread - message.port!.postMessage({ - type: "pthread", - cmd: MainToWorkerMessageType.applyConfig, - config: JSON.stringify(runtimeHelpers.config), - monoThreadInfo: JSON.stringify(worker.info), - handlers, - wasmMemory, - wasmModule - }); + { + const wasmMemory = runtimeHelpers.getMemory(); + const handlers = []; + const knownHandlers = ["onExit", "onAbort", "print", "printErr"]; + for (const handler of knownHandlers) { + if (Object.prototype.propertyIsEnumerable.call(Module, handler)) { + handlers.push(handler); + } + } + // this one shot port from setupPreloadChannelToMainThread + message.port!.postMessage({ + type: "pthread", + cmd: MainToWorkerMessageType.applyConfig, + config: JSON.stringify(runtimeHelpers.config), + monoThreadInfo: JSON.stringify(worker.info), + handlers, + wasmMemory, + wasmModule + }); + } break; case WorkerToMainMessageType.pthreadCreated: thread = new ThreadImpl(pthreadId, worker, message.port!); From 2bbcf89ea654a9ba5f1a73f75285a845f259c02f Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Thu, 18 Jun 2026 09:19:16 +0200 Subject: [PATCH 09/15] feedback --- src/mono/browser/runtime/pthreads/worker-thread.ts | 1 + src/mono/browser/runtime/startup.ts | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/browser/runtime/pthreads/worker-thread.ts b/src/mono/browser/runtime/pthreads/worker-thread.ts index 32cd178db3db83..7121a59e26d402 100644 --- a/src/mono/browser/runtime/pthreads/worker-thread.ts +++ b/src/mono/browser/runtime/pthreads/worker-thread.ts @@ -130,6 +130,7 @@ export function mono_wasm_pthread_on_pthread_registered (pthread_id: PThreadPtr) monoCmd: WorkerToMainMessageType.monoRegistered, info: monoThreadInfo, }); + runtimeHelpers.runtimeReady = true; } catch (err) { mono_log_error("mono_wasm_pthread_on_pthread_registered () failed", err); loaderHelpers.mono_exit(1, err); diff --git a/src/mono/browser/runtime/startup.ts b/src/mono/browser/runtime/startup.ts index 3d7116f8df0cab..a1a9eb6cdcf6c9 100644 --- a/src/mono/browser/runtime/startup.ts +++ b/src/mono/browser/runtime/startup.ts @@ -345,7 +345,6 @@ export function postRunWorker () { // signal next stage runtimeHelpers.runtimeReady = false; - runtimeHelpers.afterPreRun = createPromiseController(); endMeasure(mark, MeasuredBlock.postRunWorker); } catch (err) { mono_log_error("postRunWorker() failed", err); From 815554a9f7cdd6ace26fd22fcbbdf194ecacff29 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Thu, 18 Jun 2026 10:20:16 +0200 Subject: [PATCH 10/15] - drop FEATURE_WASM_MANAGED_THREADS use PlatformDetection.IsMultithreadingSupported - disable WebWorkerTest --- ...me.InteropServices.JavaScript.Tests.csproj | 5 +- .../JavaScript/JSImportTest.cs | 49 +++++++++++-------- .../JavaScript/JavaScriptTestHelper.cs | 5 +- .../JavaScript/YieldAwaitableTests.cs | 2 +- 4 files changed, 33 insertions(+), 28 deletions(-) diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System.Runtime.InteropServices.JavaScript.Tests.csproj b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System.Runtime.InteropServices.JavaScript.Tests.csproj index 15e2c682f6ecab..28eaa65f1d0ebf 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System.Runtime.InteropServices.JavaScript.Tests.csproj +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System.Runtime.InteropServices.JavaScript.Tests.csproj @@ -10,11 +10,10 @@ $(WasmXHarnessMonoArgs) --setenv=TZ=Europe/Berlin true true - $(DefineConstants);FEATURE_WASM_MANAGED_THREADS true 1 - true + true $(NoWarn);IL2103;IL2025;IL2111;IL2122 true diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs index ee9271e52c2859..7a8cbeacd36931 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs @@ -58,8 +58,8 @@ public async Task MissingImportAsync() Assert.Contains("intentionallyMissingImportAsync must be a Function but was undefined", ex.Message); } -#if !FEATURE_WASM_MANAGED_THREADS // because in MT JSHost.ImportAsync is really async, it will finish before the caller could cancel it - [Fact] + // because in MT JSHost.ImportAsync is really async, it will finish before the caller could cancel it + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMultithreadingSupported))] public async Task CancelableImportAsync() { var cts = new CancellationTokenSource(); @@ -71,7 +71,6 @@ public async Task CancelableImportAsync() var actualEx = await Assert.ThrowsAsync(async () => await JSHost.ImportAsync("JavaScriptTestHelper", "../JavaScriptTestHelper.mjs", new CancellationToken(true))); Assert.Equal("Error: OperationCanceledException", actualEx.Message); } -#endif [Fact] public unsafe void GlobalThis() @@ -286,11 +285,14 @@ public unsafe void CreateFunctionDoubleThrow() var ex = Assert.Throws(() => doubleThrows(1, 2)); Assert.Equal("Error: test 1 2", ex.Message); -#if !FEATURE_WASM_MANAGED_THREADS - Assert.Contains("create_function", ex.StackTrace); -#else - Assert.Contains("omitted JavaScript stack trace", ex.StackTrace); -#endif + if (!PlatformDetection.IsMultithreadingSupported) + { + Assert.Contains("create_function", ex.StackTrace); + } + else + { + Assert.Contains("omitted JavaScript stack trace", ex.StackTrace); + } } [Fact] @@ -1297,9 +1299,12 @@ public async Task JsImportTaskAwait() public async Task JsImportResolvedPromiseReturnsCompletedTask() { var promise = JavaScriptTestHelper.ReturnResolvedPromise(); -#if !FEATURE_WASM_MANAGED_THREADS - Assert.False(promise.IsCompleted); -#endif + + if (!PlatformDetection.IsMultithreadingSupported) + { + Assert.False(promise.IsCompleted); + } + await promise; Assert.True(promise.IsCompleted); } @@ -1587,20 +1592,22 @@ public void JsImportMath() var exThrow0 = Assert.Throws(() => JavaScriptTestHelper.throw0()); Assert.Contains("throw-0-msg", exThrow0.Message); Assert.DoesNotContain(" at ", exThrow0.Message); -#if !FEATURE_WASM_MANAGED_THREADS - Assert.Contains("throw0fn", exThrow0.StackTrace); -#else - Assert.Contains("omitted JavaScript stack trace", exThrow0.StackTrace); -#endif + if (!PlatformDetection.IsMultithreadingSupported) + { + Assert.Contains("throw0fn", exThrow0.StackTrace); + } var exThrow1 = Assert.Throws(() => throw1(value)); Assert.Contains("throw1-msg", exThrow1.Message); Assert.DoesNotContain(" at ", exThrow1.Message); -#if !FEATURE_WASM_MANAGED_THREADS - Assert.Contains("throw1fn", exThrow1.StackTrace); -#else - Assert.Contains("omitted JavaScript stack trace", exThrow0.StackTrace); -#endif + if (!PlatformDetection.IsMultithreadingSupported) + { + Assert.Contains("throw1fn", exThrow1.StackTrace); + } + else + { + Assert.Contains("omitted JavaScript stack trace", exThrow0.StackTrace); + } // anything is a system.object, sometimes it would be JSObject wrapper if (typeof(T).IsPrimitive) diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs index d1fffe5517452d..531e56179ea5f5 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs @@ -1235,10 +1235,7 @@ public static async Task InitializeAsync() await Setup(); } -#if FEATURE_WASM_MANAGED_THREADS - // are we in the UI thread ? - if (Environment.CurrentManagedThreadId == 1) -#endif + if (!PlatformDetection.IsMultithreadingSupported || Environment.CurrentManagedThreadId == 1) { // this gives browser chance to serve UI thread event loop before every test await Task.Yield(); diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/YieldAwaitableTests.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/YieldAwaitableTests.cs index f268006739441b..99a26e7eede274 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/YieldAwaitableTests.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/YieldAwaitableTests.cs @@ -22,7 +22,7 @@ public async Task TaskYieldsToBrowserLoop() Assert.True(JavaScriptTestHelper.IsPromiseThenHit()); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMultithreadingSupported))] public async Task TaskDelay0DoesNotYieldToBrowserLoop() { JavaScriptTestHelper.BeforeYield(); From ed12a1bd25a94d92e2ff631d32b55dbcfc033c8f Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Thu, 18 Jun 2026 11:19:58 +0200 Subject: [PATCH 11/15] more --- src/libraries/tests.proj | 3 +++ src/mono/browser/runtime/startup.ts | 2 +- src/mono/browser/test-main.mjs | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/libraries/tests.proj b/src/libraries/tests.proj index 3783247c6871ff..b825adca5bc245 100644 --- a/src/libraries/tests.proj +++ b/src/libraries/tests.proj @@ -583,6 +583,9 @@ + + + diff --git a/src/mono/browser/runtime/startup.ts b/src/mono/browser/runtime/startup.ts index a1a9eb6cdcf6c9..3b88f0161a4120 100644 --- a/src/mono/browser/runtime/startup.ts +++ b/src/mono/browser/runtime/startup.ts @@ -5,7 +5,7 @@ import WasmEnableThreads from "consts:wasmEnableThreads"; import BuildConfiguration from "consts:configuration"; import { DotnetModuleInternal, CharPtrNull, MainToWorkerMessageType } from "./types/internal"; -import { exportedRuntimeAPI, INTERNAL, loaderHelpers, Module, runtimeHelpers, createPromiseController, mono_assert, browserVirtualAppBase } from "./globals"; +import { exportedRuntimeAPI, INTERNAL, loaderHelpers, Module, runtimeHelpers, mono_assert, browserVirtualAppBase } from "./globals"; import cwraps, { init_c_exports, threads_c_functions as tcwraps } from "./cwraps"; import { mono_wasm_raise_debug_event, mono_wasm_runtime_ready } from "./debug"; import { toBase64StringImpl } from "./base64"; diff --git a/src/mono/browser/test-main.mjs b/src/mono/browser/test-main.mjs index 7db3721bb44eaf..b256d8bba5ab56 100644 --- a/src/mono/browser/test-main.mjs +++ b/src/mono/browser/test-main.mjs @@ -263,6 +263,7 @@ function configureRuntime(dotnet, runArgs) { dumpThreadsOnNonZeroExit: true, loadAllSatelliteResources: true, jsThreadBlockingMode: "ThrowWhenBlockingWait", + pthreadPoolInitialSize: 15, }); if (ENVIRONMENT_IS_NODE) { From 562c5a0e65a2a38343993d5f3c10b0030022e482 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Thu, 18 Jun 2026 15:45:37 +0200 Subject: [PATCH 12/15] progress on MT --- .../FunctionalTests/System.Net.Http.Functional.Tests.csproj | 2 +- .../tests/System.Net.WebSockets.Client.Tests.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj b/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj index b699cc0dafd7fd..28306cba768a64 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj @@ -29,7 +29,7 @@ $(DefineConstants);TARGET_BROWSER 01:15:00 - true + true diff --git a/src/libraries/System.Net.WebSockets.Client/tests/System.Net.WebSockets.Client.Tests.csproj b/src/libraries/System.Net.WebSockets.Client/tests/System.Net.WebSockets.Client.Tests.csproj index 83cf148c6a9fd6..49690818a4c74c 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/System.Net.WebSockets.Client.Tests.csproj +++ b/src/libraries/System.Net.WebSockets.Client/tests/System.Net.WebSockets.Client.Tests.csproj @@ -17,7 +17,7 @@ $(TestArchiveTestsRoot)$(OSPlatformConfig)/ $(DefineConstants);TARGET_BROWSER 1 - true + true From 7b5676364689bbfbcdcec1039c54d5f1e1816124 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Thu, 18 Jun 2026 18:07:52 +0200 Subject: [PATCH 13/15] fix async --- .../tests/System.Threading.Tests.csproj | 1 + .../browser/runtime/pthreads/ui-thread.ts | 30 +++++++++---------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/libraries/System.Threading/tests/System.Threading.Tests.csproj b/src/libraries/System.Threading/tests/System.Threading.Tests.csproj index cb21b19ae3a8c9..7717d16d56bf31 100644 --- a/src/libraries/System.Threading/tests/System.Threading.Tests.csproj +++ b/src/libraries/System.Threading/tests/System.Threading.Tests.csproj @@ -7,6 +7,7 @@ <_WasmPThreadPoolUnusedSize>10 + true diff --git a/src/mono/browser/runtime/pthreads/ui-thread.ts b/src/mono/browser/runtime/pthreads/ui-thread.ts index 6027a041d9d987..8a2e4f7fed7fe9 100644 --- a/src/mono/browser/runtime/pthreads/ui-thread.ts +++ b/src/mono/browser/runtime/pthreads/ui-thread.ts @@ -213,26 +213,26 @@ export function replaceEmscriptenPThreadUI (modulePThread: PThreadLibrary): void const originalLoadWasmModuleToWorker = modulePThread.loadWasmModuleToWorker; const originalReturnWorkerToPool = modulePThread.returnWorkerToPool; - modulePThread.loadWasmModuleToWorker = async (worker: PThreadWorker): Promise => { - const wasmModule = await loaderHelpers.wasmCompilePromise.promise; - - // Stop queueing once we can process messages synchronously. - if (worker.handler) { - worker.removeEventListener("message", worker.handler); - } - - for (const queuedEvent of worker.queue) { - monoWorkerMessageHandler(worker, wasmModule, queuedEvent); - } - - worker.queue.length = 0; - worker.handler = (ev) => monoWorkerMessageHandler(worker, wasmModule, ev); - worker.addEventListener("message", worker.handler); + modulePThread.loadWasmModuleToWorker = (worker: PThreadWorker): Promise => { const afterLoaded = originalLoadWasmModuleToWorker(worker); afterLoaded.then(() => { worker.info.isLoaded = true; }); + + loaderHelpers.wasmCompilePromise.promise.then((wasmModule) => { + // Stop queueing once we can process messages synchronously. + for (const queuedEvent of worker.queue) { + monoWorkerMessageHandler(worker, wasmModule, queuedEvent); + } + worker.queue.length = 0; + if (worker.handler) { + worker.removeEventListener("message", worker.handler); + } + worker.handler = (ev) => monoWorkerMessageHandler(worker, wasmModule, ev); + worker.addEventListener("message", worker.handler); + }); + if (loaderHelpers.config.exitOnUnhandledError) { worker.onerror = (e) => { loaderHelpers.mono_exit(1, e); From e0e722213fd5be220a5d7ffca5157d05d82523f1 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Thu, 18 Jun 2026 18:33:33 +0200 Subject: [PATCH 14/15] fix pthread creation --- src/mono/browser/runtime/startup.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mono/browser/runtime/startup.ts b/src/mono/browser/runtime/startup.ts index 3b88f0161a4120..343283935a6cda 100644 --- a/src/mono/browser/runtime/startup.ts +++ b/src/mono/browser/runtime/startup.ts @@ -144,9 +144,10 @@ async function instantiateWasmWorker ( // Instantiate from the module posted from the main thread. // We can just use sync instantiation in the worker. const instance = new WebAssembly.Instance(Module.wasmModule!, imports); - successCallback(instance, undefined); Module.wasmModule = null; + preRunWorker(); + successCallback(instance, undefined); } From 4e8f7580e6e4a204cd700d0ce83428a0e6029a2d Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Thu, 18 Jun 2026 18:42:36 +0200 Subject: [PATCH 15/15] feedback, cleanup --- ...me.InteropServices.JavaScript.Tests.csproj | 13 - .../JavaScript/WebWorkerTest.Http.cs | 94 --- .../JavaScript/WebWorkerTest.WebSocket.cs | 111 --- .../JavaScript/WebWorkerTest.cs | 646 ------------------ .../JavaScript/WebWorkerTestBase.cs | 225 ------ .../JavaScript/WebWorkerTestHelper.cs | 403 ----------- .../JavaScript/WebWorkerTestHelper.mjs | 85 --- .../InteropServices/JavaScript/test.json | 3 - 8 files changed, 1580 deletions(-) delete mode 100644 src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.Http.cs delete mode 100644 src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.WebSocket.cs delete mode 100644 src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.cs delete mode 100644 src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTestBase.cs delete mode 100644 src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTestHelper.cs delete mode 100644 src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTestHelper.mjs delete mode 100644 src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/test.json diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System.Runtime.InteropServices.JavaScript.Tests.csproj b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System.Runtime.InteropServices.JavaScript.Tests.csproj index 28eaa65f1d0ebf..d5a40b9c28f982 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System.Runtime.InteropServices.JavaScript.Tests.csproj +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System.Runtime.InteropServices.JavaScript.Tests.csproj @@ -61,17 +61,4 @@ - - diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.Http.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.Http.cs deleted file mode 100644 index 4550bef70dd4f5..00000000000000 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.Http.cs +++ /dev/null @@ -1,94 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Threading.Tasks; -using System.Threading; -using System.Net.Http; -using Xunit; -using System.IO; -using System.Text; - -namespace System.Runtime.InteropServices.JavaScript.Tests -{ - public class WebWorkerHttpTest : WebWorkerTestBase - { - #region HTTP - - [Theory, MemberData(nameof(GetTargetThreads))] - public async Task HttpClient_ContentInSameThread(Executor executor) - { - using var cts = CreateTestCaseTimeoutSource(); - var uri = WebWorkerTestHelper.GetOriginUrl() + "/test.json"; - - await executor.Execute(async () => - { - using var client = new HttpClient(); - using var response = await client.GetAsync(uri); - response.EnsureSuccessStatusCode(); - var body = await response.Content.ReadAsStringAsync(); - Assert.Contains("hello", body); - Assert.Contains("world", body); - }, cts.Token); - } - - private static HttpRequestOptionsKey WebAssemblyEnableStreamingRequestKey = new("WebAssemblyEnableStreamingRequest"); - private static string HelloJson = "{'hello':'world'}".Replace('\'', '"'); - private static string EchoStart = "{\"Method\":\"POST\",\"Url\":\"/Echo.ashx"; - - private async Task HttpClient_ActionInDifferentThread(string url, Executor executor1, Executor executor2, Func e2Job) - { - using var cts = CreateTestCaseTimeoutSource(); - - var e1Job = async (Task e2done, TaskCompletionSource e1State) => - { - using var ms = new MemoryStream(); - await ms.WriteAsync(Encoding.UTF8.GetBytes(HelloJson)); - - using var req = new HttpRequestMessage(HttpMethod.Post, url); - req.Content = new StreamContent(ms); - using var client = new HttpClient(); - var pr = client.SendAsync(req, HttpCompletionOption.ResponseHeadersRead); - using var response = await pr; - - // share the state with the E2 continuation - e1State.SetResult(response); - - await e2done; - }; - await ActionsInDifferentThreads(executor1, executor2, e1Job, e2Job, cts); - } - - [Theory, MemberData(nameof(GetTargetThreads2x))] - public async Task HttpClient_ContentInDifferentThread(Executor executor1, Executor executor2) - { - var url = WebWorkerTestHelper.LocalHttpEcho + "?guid=" + Guid.NewGuid(); - await HttpClient_ActionInDifferentThread(url, executor1, executor2, async (HttpResponseMessage response) => - { - response.EnsureSuccessStatusCode(); - var body = await response.Content.ReadAsStringAsync(); - Assert.StartsWith(EchoStart, body); - }); - } - - [ActiveIssue("https://github.com/dotnet/runtime/issues/98101")] - [Theory, MemberData(nameof(GetTargetThreads2x))] - public async Task HttpClient_CancelInDifferentThread(Executor executor1, Executor executor2) - { - var url = WebWorkerTestHelper.LocalHttpEcho + "?delay10sec=true&guid=" + Guid.NewGuid(); - await HttpClient_ActionInDifferentThread(url, executor1, executor2, async (HttpResponseMessage response) => - { - await Assert.ThrowsAsync(async () => - { - CancellationTokenSource cts = new CancellationTokenSource(); - var promise = response.Content.ReadAsStringAsync(cts.Token); - WebWorkerTestHelper.Log("HttpClient_CancelInDifferentThread: ManagedThreadId: " + Environment.CurrentManagedThreadId + " NativeThreadId: " + WebWorkerTestHelper.NativeThreadId); - cts.Cancel(); - var res = await promise; - throw new Exception("This should be unreachable: " + res); - }); - }); - } - - #endregion - } -} diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.WebSocket.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.WebSocket.cs deleted file mode 100644 index 10153ee995ecc8..00000000000000 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.WebSocket.cs +++ /dev/null @@ -1,111 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Threading.Tasks; -using System.Threading; -using Xunit; -using System.Net.WebSockets; -using System.Text; - -namespace System.Runtime.InteropServices.JavaScript.Tests -{ - public class WebWorkerWebSocketTest : WebWorkerTestBase - { - #region WebSocket - - [Theory, MemberData(nameof(GetTargetThreads))] - public async Task WebSocketClient_ContentInSameThread(Executor executor) - { - using var cts = CreateTestCaseTimeoutSource(); - - var uri = new Uri(WebWorkerTestHelper.LocalWsEcho + "?guid=" + Guid.NewGuid()); - var message = "hello"; - var send = Encoding.UTF8.GetBytes(message); - var receive = new byte[100]; - - await executor.Execute(async () => - { - using var client = new ClientWebSocket(); - await client.ConnectAsync(uri, CancellationToken.None); - await client.SendAsync(send, WebSocketMessageType.Text, true, CancellationToken.None); - - var res = await client.ReceiveAsync(receive, CancellationToken.None); - Assert.Equal(WebSocketMessageType.Text, res.MessageType); - Assert.True(res.EndOfMessage); - Assert.Equal(send.Length, res.Count); - Assert.Equal(message, Encoding.UTF8.GetString(receive, 0, res.Count)); - }, cts.Token); - } - - - [Theory, MemberData(nameof(GetTargetThreads2x))] - public async Task WebSocketClient_ResponseCloseInDifferentThread(Executor executor1, Executor executor2) - { - using var cts = CreateTestCaseTimeoutSource(); - - var uri = new Uri(WebWorkerTestHelper.LocalWsEcho + "?guid=" + Guid.NewGuid()); - var message = "hello"; - var send = Encoding.UTF8.GetBytes(message); - var receive = new byte[100]; - - var e1Job = async (Task e2done, TaskCompletionSource e1State) => - { - using var client = new ClientWebSocket(); - await client.ConnectAsync(uri, CancellationToken.None); - await client.SendAsync(send, WebSocketMessageType.Text, true, CancellationToken.None); - - // share the state with the E2 continuation - e1State.SetResult(client); - await e2done; - }; - - var e2Job = async (ClientWebSocket client) => - { - var res = await client.ReceiveAsync(receive, CancellationToken.None); - Assert.Equal(WebSocketMessageType.Text, res.MessageType); - Assert.True(res.EndOfMessage); - Assert.Equal(send.Length, res.Count); - Assert.Equal(message, Encoding.UTF8.GetString(receive, 0, res.Count)); - - await client.CloseAsync(WebSocketCloseStatus.NormalClosure, "bye", CancellationToken.None); - }; - - await ActionsInDifferentThreads(executor1, executor2, e1Job, e2Job, cts); - } - - [Theory, MemberData(nameof(GetTargetThreads2x))] - public async Task WebSocketClient_CancelInDifferentThread(Executor executor1, Executor executor2) - { - using var cts = CreateTestCaseTimeoutSource(); - - var uri = new Uri(WebWorkerTestHelper.LocalWsEcho + "?guid=" + Guid.NewGuid()); - var message = ".delay5sec"; // this will make the loopback server slower - var send = Encoding.UTF8.GetBytes(message); - var receive = new byte[100]; - - var e1Job = async (Task e2done, TaskCompletionSource e1State) => - { - using var client = new ClientWebSocket(); - await client.ConnectAsync(uri, CancellationToken.None); - await client.SendAsync(send, WebSocketMessageType.Text, true, CancellationToken.None); - - // share the state with the E2 continuation - e1State.SetResult(client); - await e2done; - }; - - var e2Job = async (ClientWebSocket client) => - { - CancellationTokenSource cts2 = new CancellationTokenSource(); - var resTask = client.ReceiveAsync(receive, cts2.Token); - cts2.Cancel(); - var ex = await Assert.ThrowsAnyAsync(() => resTask); - Assert.Equal(cts2.Token, ex.CancellationToken); - }; - - await ActionsInDifferentThreads(executor1, executor2, e1Job, e2Job, cts); - } - - #endregion - } -} diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.cs deleted file mode 100644 index 278270b470b871..00000000000000 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.cs +++ /dev/null @@ -1,646 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.IO; -using System.Threading.Tasks; -using System.Threading; -using Xunit; - -namespace System.Runtime.InteropServices.JavaScript.Tests -{ - - // TODO test: - // JSExport 2x - // ProxyContext flow, child thread, child task - // use JSObject after JSWebWorker finished, especially HTTP - // event pipe - // JS setTimeout till after JSWebWorker close - // synchronous .Wait for JS setTimeout on the same thread -> deadlock problem **7)** - - public class WebWorkerTest : WebWorkerTestBase - { - #region Executors - - [Theory, MemberData(nameof(GetTargetThreads))] - public async Task Executor_Cancellation(Executor executor) - { - var cts = new CancellationTokenSource(); - - TaskCompletionSource ready = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var canceledTask = executor.Execute(() => - { - TaskCompletionSource never = new TaskCompletionSource(); - ready.SetResult(); - return never.Task; - }, cts.Token); - - await ready.Task; - - cts.Cancel(); - - await Assert.ThrowsAnyAsync(() => canceledTask); - } - - [Theory, MemberData(nameof(GetTargetThreads))] - public async Task JSDelay_Cancellation(Executor executor) - { - var cts = new CancellationTokenSource(); - TaskCompletionSource ready = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var canceledTask = executor.Execute(async () => - { - await executor.StickyAwait(WebWorkerTestHelper.CreateDelay(), cts.Token); - - var never = WebWorkerTestHelper.JSDelay(int.MaxValue); - ready.SetResult(); - await never; - }, cts.Token); - - await ready.Task; - - cts.Cancel(); - - await Assert.ThrowsAnyAsync(() => canceledTask); - } - - [Theory, MemberData(nameof(GetBlockingFriendlyTargetThreads))] - public async Task JSDelay_Blocking_Wait(Executor executor) - { - var cts = new CancellationTokenSource(); - var blockedTask = executor.Execute(async () => - { - await executor.StickyAwait(WebWorkerTestHelper.CreateDelay(), cts.Token); - var promise = WebWorkerTestHelper.JSDelay(100); - promise.Wait(); - }, cts.Token); - - await blockedTask; - } - - [Theory, MemberData(nameof(GetBlockingFriendlyTargetThreads))] - public async Task JSDelay_Blocking_GetResult(Executor executor) - { - var cts = new CancellationTokenSource(); - var blockedTask = executor.Execute(async () => - { - await executor.StickyAwait(WebWorkerTestHelper.CreateDelay(), cts.Token); - var promise = WebWorkerTestHelper.JSDelay(100); - promise.GetAwaiter().GetResult(); - }, cts.Token); - - await blockedTask; - } - - [Fact] - public async Task JSSynchronizationContext_Send_Post_Items_Cancellation() - { - var cts = new CancellationTokenSource(); - - ManualResetEventSlim blocker = new ManualResetEventSlim(false); - TaskCompletionSource never = new TaskCompletionSource(); - TaskCompletionSource canceled = new TaskCompletionSource(); - SynchronizationContext capturedSynchronizationContext = null; - TaskCompletionSource jswReady = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - TaskCompletionSource sendReady = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - TaskCompletionSource postReady = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var canceledTask = JSWebWorker.RunAsync(() => - { - capturedSynchronizationContext = SynchronizationContext.Current; - jswReady.SetResult(); - - // blocking the worker, so that JSSynchronizationContext could enqueue next tasks - Thread.ForceBlockingWait(static (b) => ((ManualResetEventSlim)b).Wait(), blocker); - - return never.Task; - }, cts.Token); - - await jswReady.Task; - Assert.Equal("System.Runtime.InteropServices.JavaScript.JSSynchronizationContext", capturedSynchronizationContext!.GetType().FullName); - - var shouldNotHitSend = false; - var shouldNotHitPost = false; - var hitAfterPost = false; - var hitAfterSend = false; - - var canceledSend = Task.Run(() => - { - sendReady.SetResult(); - // this will be blocked until blocker.Set() - try - { - capturedSynchronizationContext.Send(_ => - { - // then it should get canceled and not executed - shouldNotHitSend = true; - }, null); - } - catch (Exception ex) - { - return Task.FromException(ex); - } - hitAfterSend = true; - return Task.FromException(new Exception("Should be unreachable")); - }); - - var canceledPost = Task.Run(() => - { - try - { - capturedSynchronizationContext.Post(_ => - { - // then it should get canceled and not executed - shouldNotHitPost = true; - }, null); - } - catch (Exception ex) - { - WebWorkerTestHelper.Log("Unexpected exception " + ex); - postReady.SetException(ex); - return Task.FromException(ex); - } - hitAfterPost = true; - postReady.SetResult(); - return Task.CompletedTask; - }); - - // make sure that jobs got the chance to enqueue - await postReady.Task; - await sendReady.Task; - await Task.Delay(200); // make sure that - - // this could should be delivered immediately - cts.Cancel(); - - // now we release helpers to use capturedSynchronizationContext - canceled.SetResult(); - - // this will unblock the current pending work item - blocker.Set(); - - await Assert.ThrowsAnyAsync(() => canceledSend); - await canceledPost; // this shouldn't throw - - Assert.False(shouldNotHitSend); - Assert.False(shouldNotHitPost); - Assert.True(hitAfterPost); - Assert.False(hitAfterSend); - } - - [Fact] - public async Task JSSynchronizationContext_Send_Post_To_Canceled() - { - var cts = new CancellationTokenSource(); - - TaskCompletionSource never = new TaskCompletionSource(); - SynchronizationContext capturedSynchronizationContext = null; - TaskCompletionSource jswReady = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - JSObject capturedGlobalThis = null; - - var canceledTask = JSWebWorker.RunAsync(() => - { - capturedSynchronizationContext = SynchronizationContext.Current; - capturedGlobalThis = JSHost.GlobalThis; - jswReady.SetResult(); - return never.Task; - }, cts.Token); - - await jswReady.Task; - Assert.Equal("System.Runtime.InteropServices.JavaScript.JSSynchronizationContext", capturedSynchronizationContext!.GetType().FullName); - - cts.Cancel(); - - // give it chance to dispose the thread - await Task.Delay(100); - - Assert.True(capturedGlobalThis.IsDisposed); - - var shouldNotHitSend = false; - var shouldNotHitPost = false; - - Assert.Throws(() => - { - capturedGlobalThis.HasProperty("document"); - }); - - Assert.Throws(() => - { - capturedSynchronizationContext.Send(_ => - { - // then it should get canceled and not executed - shouldNotHitSend = true; - }, null); - }); - - Assert.Throws(() => - { - capturedSynchronizationContext.Post(_ => - { - // then it should get canceled and not executed - shouldNotHitPost = true; - }, null); - }); - - Assert.False(shouldNotHitSend); - Assert.False(shouldNotHitPost); - } - - [ActiveIssue("https://github.com/dotnet/runtime/issues/96628#issuecomment-1907602744")] - [Fact] - // this will say something like `JSSynchronizationContext is still installed on worker 0x4ff0030.` in the console during shutdown. - public async Task JSWebWorker_Abandon_Running() - { - TaskCompletionSource never = new TaskCompletionSource(); - TaskCompletionSource ready = new TaskCompletionSource(); - -#pragma warning disable CS4014 - // intentionally not awaiting it - JSWebWorker.RunAsync(() => - { - ready.SetResult(); - return never.Task; - }, CancellationToken.None); -#pragma warning restore CS4014 - - await ready.Task; - - // it should not get collected - GC.Collect(); - - // it should not prevent mono and the test suite from exiting - } - - [Fact] - // this will say something like `JSSynchronizationContext is still installed on worker 0x4ff0030.` in the console during shutdown. - public async Task JSWebWorker_Abandon_Running_JS() - { - TaskCompletionSource ready = new TaskCompletionSource(); - -#pragma warning disable CS4014 - // intentionally not awaiting it - JSWebWorker.RunAsync(async () => - { - await WebWorkerTestHelper.CreateDelay(); - var never = WebWorkerTestHelper.JSDelay(int.MaxValue); - ready.SetResult(); - await never; - }, CancellationToken.None); -#pragma warning restore CS4014 - - await ready.Task; - - // it should not get collected - GC.Collect(); - - // it should not prevent mono and the test suite from exiting - } - - [Theory, MemberData(nameof(GetTargetThreads))] - public async Task Executor_Propagates(Executor executor) - { - using var cts = CreateTestCaseTimeoutSource(); - bool hit = false; - var failedTask = executor.Execute(() => - { - hit = true; - throw new InvalidOperationException("Test"); - }, cts.Token); - - var ex = await Assert.ThrowsAsync(() => failedTask); - Assert.True(hit); - Assert.Equal("Test", ex.Message); - } - - [Theory, MemberData(nameof(GetTargetThreads))] - public async Task Executor_Propagates_After_Delay(Executor executor) - { - using var cts = CreateTestCaseTimeoutSource(); - bool hit = false; - var failedTask = executor.Execute(async () => - { - await executor.StickyAwait(WebWorkerTestHelper.CreateDelay(), cts.Token); - await WebWorkerTestHelper.JSDelay(10); - - hit = true; - throw new InvalidOperationException("Test"); - }, cts.Token); - - var ex = await Assert.ThrowsAsync(async () => await failedTask); - Assert.True(hit); - Assert.Equal("Test", ex.Message); - } - - #endregion - - #region Console, Yield, Delay, Timer - - [Theory, MemberData(nameof(GetTargetThreads))] - public async Task ManagedConsole(Executor executor) - { - using var cts = CreateTestCaseTimeoutSource(); - await executor.Execute(() => - { - WebWorkerTestHelper.Log("C# Hello from ManagedThreadId: " + Environment.CurrentManagedThreadId); - Console.Clear(); - return Task.CompletedTask; - }, cts.Token); - } - - [Theory, MemberData(nameof(GetTargetThreads))] - public async Task JSConsole(Executor executor) - { - using var cts = CreateTestCaseTimeoutSource(); - await executor.Execute(() => - { - WebWorkerTestHelper.Log("JS Hello from ManagedThreadId: " + Environment.CurrentManagedThreadId + " NativeThreadId: " + WebWorkerTestHelper.NativeThreadId); - return Task.CompletedTask; - }, cts.Token); - } - - [Theory, MemberData(nameof(GetTargetThreads))] - public async Task NativeThreadId(Executor executor) - { - using var cts = CreateTestCaseTimeoutSource(); - await executor.Execute(async () => - { - await executor.StickyAwait(WebWorkerTestHelper.InitializeAsync(), cts.Token); - - var jsTid = WebWorkerTestHelper.GetTid(); - var csTid = WebWorkerTestHelper.NativeThreadId; - if (executor.Type == ExecutorType.JSWebWorker) - { - Assert.Equal(jsTid, csTid); - } - else - { - Assert.NotEqual(jsTid, csTid); - } - - await WebWorkerTestHelper.DisposeAsync(); - }, cts.Token); - } - - [Theory, MemberData(nameof(GetTargetThreads))] - public async Task ThreadingTimer(Executor executor) - { - var hit = false; - using var cts = CreateTestCaseTimeoutSource(); - await executor.Execute(async () => - { - TaskCompletionSource tcs = new TaskCompletionSource(); - WebWorkerTestHelper.Log("ThreadingTimer: Start Time: " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") + " ManagedThreadId: " + Environment.CurrentManagedThreadId + " NativeThreadId: " + WebWorkerTestHelper.NativeThreadId); - - using var timer = new Timer(_ => - { - Assert.NotEqual(1, Environment.CurrentManagedThreadId); - Assert.True(Thread.CurrentThread.IsThreadPoolThread); - hit = true; - tcs.SetResult(); - }, null, 100, Timeout.Infinite); - - await tcs.Task; - }, cts.Token); - - WebWorkerTestHelper.Log("ThreadingTimer: End Time: " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") + " ManagedThreadId: " + Environment.CurrentManagedThreadId + " NativeThreadId: " + WebWorkerTestHelper.NativeThreadId); - Assert.True(hit); - } - - [Theory, MemberData(nameof(GetTargetThreads))] - public async Task JSDelay_ContinueWith_Async(Executor executor) - { - using var cts = CreateTestCaseTimeoutSource(); - await executor.Execute(async () => - { - await executor.StickyAwait(WebWorkerTestHelper.CreateDelay(), cts.Token); - - await WebWorkerTestHelper.JSDelay(10).ContinueWith(_ => - { - Assert.True(Thread.CurrentThread.IsThreadPoolThread); - }, TaskContinuationOptions.RunContinuationsAsynchronously); - }, cts.Token); - } - - [Theory, MemberData(nameof(GetTargetThreads))] - public async Task JSDelay_ContinueWith_Sync(Executor executor) - { - using var cts = CreateTestCaseTimeoutSource(); - await executor.Execute(async () => - { - await executor.StickyAwait(WebWorkerTestHelper.CreateDelay(), cts.Token); - - await WebWorkerTestHelper.JSDelay(10).ContinueWith(_ => - { - Assert.True(Thread.CurrentThread.IsThreadPoolThread); - }, TaskContinuationOptions.ExecuteSynchronously); // ExecuteSynchronously is ignored - }, cts.Token); - } - - [Theory, MemberData(nameof(GetTargetThreads))] - public async Task JSDelay_ConfigureAwait_True(Executor executor) - { - using var cts = CreateTestCaseTimeoutSource(); - await executor.Execute(async () => - { - await executor.StickyAwait(WebWorkerTestHelper.CreateDelay(), cts.Token); - - await WebWorkerTestHelper.JSDelay(10).ConfigureAwait(true); - - executor.AssertAwaitCapturedContext(); - }, cts.Token); - } - - [Theory, MemberData(nameof(GetTargetThreads))] - public async Task JSDelay_ConfigureAwait_False(Executor executor) - { - using var cts = CreateTestCaseTimeoutSource(); - await executor.Execute(async () => - { - await executor.StickyAwait(WebWorkerTestHelper.CreateDelay(), cts.Token); - - await WebWorkerTestHelper.JSDelay(10).ConfigureAwait(false); - - // resolve/reject on I/O thread -> thread pool - Assert.True(Thread.CurrentThread.IsThreadPoolThread); - }, cts.Token); - } - - [Theory, MemberData(nameof(GetTargetThreads))] - public async Task ManagedDelay_ContinueWith(Executor executor) - { - var hit = false; - using var cts = CreateTestCaseTimeoutSource(); - await executor.Execute(async () => - { - await Task.Delay(10, cts.Token).ContinueWith(_ => - { - hit = true; - }, TaskContinuationOptions.ExecuteSynchronously); - }, cts.Token); - Assert.True(hit); - } - - [Theory, MemberData(nameof(GetTargetThreads))] - public async Task ManagedDelay_ConfigureAwait_True(Executor executor) - { - using var cts = CreateTestCaseTimeoutSource(); - await executor.Execute(async () => - { - await Task.Delay(10, cts.Token).ConfigureAwait(true); - - executor.AssertAwaitCapturedContext(); - }, cts.Token); - } - - [Theory, MemberData(nameof(GetTargetThreadsAndBlockingCalls))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/99951")] - public async Task WaitInAsyncAssertsOnlyOnJSWebWorker(Executor executor, NamedCall method) - { - using var cts = CreateTestCaseTimeoutSource(); - await executor.Execute(async () => - { - await executor.StickyAwait(WebWorkerTestHelper.InitializeAsync(), cts.Token); - - Exception? exception = null; - try - { - method.Call(cts.Token); - } - catch (Exception ex) - { - exception = ex; - } - - if (method.IsBlocking && executor.Type == ExecutorType.JSWebWorker) - { - Assert.NotNull(exception); - Assert.IsType(exception); - } - else - { - Assert.Null(exception); - } - }, cts.Token); - } - - [Theory, MemberData(nameof(GetTargetThreadsAndBlockingCalls))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/99951")] - public async Task WaitAssertsOnSyncCallback(Executor executor, NamedCall method) - { - using var cts = CreateTestCaseTimeoutSource(); - await executor.Execute(async () => - { - await executor.StickyAwait(WebWorkerTestHelper.InitializeAsync(), cts.Token); - - Exception? exception = null; - // the callback will hit Main or JSWebWorker, not the original executor thread - await WebWorkerTestHelper.CallMeBackSync(() => - { - // when we are inside of synchronous callback, all blocking .Wait is forbidden - try - { - method.Call(cts.Token); - } - catch (Exception ex) - { - exception = ex; - } - }); - - if (method.IsBlocking) - { - Assert.NotNull(exception); - Assert.IsType(exception); - } - else - { - Assert.Null(exception); - } - }, cts.Token); - } - - [Theory, MemberData(nameof(GetTargetThreadsAndBlockingCalls))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/99951")] - public async Task WaitAssertsOnSyncJSExport(Executor executor, NamedCall method) - { - using var cts = CreateTestCaseTimeoutSource(); - await executor.Execute(async () => - { - await executor.StickyAwait(WebWorkerTestHelper.InitializeAsync(), cts.Token); - - WebWorkerTestHelper.CurrentCallback = method; - WebWorkerTestHelper.CurrentCancellationToken = cts.Token; - // the callback will hit Main or JSWebWorker, not the original executor thread - await WebWorkerTestHelper.CallExportBackSync(nameof(WebWorkerTestHelper.CallCurrentCallback)); - - if (method.IsBlocking) - { - Assert.NotNull(WebWorkerTestHelper.LastException); - Assert.IsType(WebWorkerTestHelper.LastException); - } - else - { - Assert.Null(WebWorkerTestHelper.LastException); - } - }, cts.Token); - } - - [Theory, MemberData(nameof(GetTargetThreads))] - public async Task ManagedYield(Executor executor) - { - using var cts = CreateTestCaseTimeoutSource(); - await executor.Execute(async () => - { - await Task.Yield(); - - executor.AssertAwaitCapturedContext(); - }, cts.Token); - } - - [Fact] - public async Task FinalizerWorks() - { - var ft = new FinalizerTest(); - GC.Collect(); - await Task.Delay(100); - GC.Collect(); - await Task.Delay(100); - Assert.True(FinalizerTest.FinalizerHit); - } - - #endregion - - #region Thread Affinity - - [Theory, MemberData(nameof(GetTargetThreads2x))] - public async Task JSObject_CapturesAffinity(Executor executor1, Executor executor2) - { - using var cts = CreateTestCaseTimeoutSource(); - - var e1Job = async (Task e2done, TaskCompletionSource e1State) => - { - await WebWorkerTestHelper.InitializeAsync(); - - executor1.AssertAwaitCapturedContext(); - - var jsState = await WebWorkerTestHelper.PromiseState(); - - // share the state with the E2 continuation - e1State.SetResult(jsState); - - await e2done; - - // cleanup - await WebWorkerTestHelper.DisposeAsync(); - }; - - var e2Job = async (JSObject e1State) => - { - bool valid = await WebWorkerTestHelper.PromiseValidateState(e1State); - Assert.True(valid); - }; - - await ActionsInDifferentThreads(executor1, executor2, e1Job, e2Job, cts); - } - - #endregion - - } -} diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTestBase.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTestBase.cs deleted file mode 100644 index c9b8304978a4fa..00000000000000 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTestBase.cs +++ /dev/null @@ -1,225 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.IO; -using System.Threading.Tasks; -using System.Threading; -using Xunit; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; - -namespace System.Runtime.InteropServices.JavaScript.Tests -{ - public class WebWorkerTestBase : IAsyncLifetime - { - const int TimeoutMilliseconds = 5000; - - public static bool _isWarmupDone; - - public async Task InitializeAsync() - { - if (_isWarmupDone) - { - return; - } - await Task.Delay(500); - _isWarmupDone = true; - } - - public Task DisposeAsync() => Task.CompletedTask; - - protected CancellationTokenSource CreateTestCaseTimeoutSource([CallerMemberName] string memberName = "") - { - var start = DateTime.Now; - var cts = new CancellationTokenSource(TimeoutMilliseconds); - cts.Token.Register(() => - { - var end = DateTime.Now; - WebWorkerTestHelper.Log($"Unexpected test case {memberName} timeout after {end - start} ManagedThreadId:{Environment.CurrentManagedThreadId}"); - }); - return cts; - } - - public static IEnumerable GetTargetThreads() - { - return Enum.GetValues().Select(type => new object[] { new Executor(type) }); - } - - public static IEnumerable GetBlockingFriendlyTargetThreads() - { - yield return new object[] { new Executor(ExecutorType.Main) }; - yield return new object[] { new Executor(ExecutorType.NewThread) }; - yield return new object[] { new Executor(ExecutorType.ThreadPool) }; - // JSWebWorker is missing here because JS can't resolve promises while blocked - } - - public static IEnumerable GetSpecificTargetThreads2x() - { - yield return new object[] { new Executor(ExecutorType.Main), new Executor(ExecutorType.Main) }; - yield break; - } - - public static IEnumerable GetTargetThreads2x() - { - return Enum.GetValues().SelectMany( - type1 => Enum.GetValues().Select( - type2 => new object[] { new Executor(type1), new Executor(type2) })); - } - - protected async Task ActionsInDifferentThreads(Executor executor1, Executor executor2, Func, Task> e1Job, Func e2Job, CancellationTokenSource cts) - { - TaskCompletionSource job1ReadyTCS = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - TaskCompletionSource job2DoneTCS = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var e1Done = false; - var e2Done = false; - var e1Failed = false; - Task e1; - Task e2; - T r1; - - async Task ActionsInDifferentThreads1() - { - try - { - await e1Job(job2DoneTCS.Task, job1ReadyTCS); - if (!job1ReadyTCS.Task.IsCompleted) - { - job1ReadyTCS.SetResult(default); - } - await job2DoneTCS.Task; - } - catch (Exception ex) - { - WebWorkerTestHelper.Log("ActionsInDifferentThreads1 failed\n" + ex); - job1ReadyTCS.SetResult(default); - e1Failed = true; - throw; - } - finally - { - e1Done = true; - } - } - - async Task ActionsInDifferentThreads2() - { - try - { - await e2Job(r1); - } - finally - { - e2Done = true; - } - } - - - e1 = executor1.Execute(ActionsInDifferentThreads1, cts.Token); - r1 = await job1ReadyTCS.Task.ConfigureAwait(true); - if (e1Failed || e1.IsFaulted) - { - await e1; - } - e2 = executor2.Execute(ActionsInDifferentThreads2, cts.Token); - - try - { - await e2; - job2DoneTCS.SetResult(); - await e1; - } - catch (Exception ex) - { - job2DoneTCS.TrySetException(ex); - if (ex is OperationCanceledException oce && cts.Token.IsCancellationRequested) - { - throw; - } - if (!e1Done || !e2Done) - { - WebWorkerTestHelper.Log("ActionsInDifferentThreads canceling because of unexpected fail: \n" + ex); - cts.Cancel(); - } - else - { - WebWorkerTestHelper.Log("ActionsInDifferentThreads failed with: \n" + ex); - } - throw; - } - } - - static void LocalCtsIgnoringCall(Action action) - { - var cts = new CancellationTokenSource(8); - try - { - action(cts.Token); - } - catch (OperationCanceledException exception) - { - if (exception.CancellationToken != cts.Token) - { - throw; - } - /* ignore the local one */ - } - } - - public static IEnumerable BlockingCalls = new List - { - // things that should NOT throw PNSE - new NamedCall { IsBlocking = false, Name = "Console.WriteLine", Call = delegate (CancellationToken ct) { Console.WriteLine("Blocking"); }}, - new NamedCall { IsBlocking = false, Name = "Directory.GetCurrentDirectory", Call = delegate (CancellationToken ct) { Directory.GetCurrentDirectory(); }}, - new NamedCall { IsBlocking = false, Name = "CancellationTokenSource.ctor", Call = delegate (CancellationToken ct) { - using var cts = new CancellationTokenSource(8); - }}, - new NamedCall { IsBlocking = false, Name = "Task.Delay", Call = delegate (CancellationToken ct) { - Task.Delay(30, ct); - }}, - new NamedCall { IsBlocking = false, Name = "new Timer", Call = delegate (CancellationToken ct) { - new Timer((_) => { }, null, 1, -1); - }}, - new NamedCall { IsBlocking = false, Name = "JSType.DiscardNoWait", Call = delegate (CancellationToken ct) { - WebWorkerTestHelper.Log("DiscardNoWait"); - }}, - - // things which should throw PNSE on sync JSExport and JSWebWorker - new NamedCall { IsBlocking = true, Name = "Task.Wait", Call = delegate (CancellationToken ct) { Task.Delay(30, ct).Wait(ct); }}, - new NamedCall { IsBlocking = true, Name = "Task.WaitAll", Call = delegate (CancellationToken ct) { Task.WaitAll(Task.Delay(30, ct)); }}, - new NamedCall { IsBlocking = true, Name = "Task.WaitAny", Call = delegate (CancellationToken ct) { Task.WaitAny(Task.Delay(30, ct)); }}, - new NamedCall { IsBlocking = true, Name = "ManualResetEventSlim.Wait", Call = delegate (CancellationToken ct) { - using var mr = new ManualResetEventSlim(false); - LocalCtsIgnoringCall(mr.Wait); - }}, - new NamedCall { IsBlocking = true, Name = "SemaphoreSlim.Wait", Call = delegate (CancellationToken ct) { - using var sem = new SemaphoreSlim(2); - LocalCtsIgnoringCall(sem.Wait); - }}, - new NamedCall { IsBlocking = true, Name = "Mutex.WaitOne", Call = delegate (CancellationToken ct) { - using var mr = new ManualResetEventSlim(false); - var mutex = new Mutex(); - var thread = new Thread(() => { - mutex.WaitOne(); - mr.Set(); - Thread.Sleep(50); - mutex.ReleaseMutex(); - }); - thread.Start(); - Thread.ForceBlockingWait(static (b) => ((ManualResetEventSlim)b).Wait(), mr); - mutex.WaitOne(); - }}, - }; - - public static IEnumerable GetTargetThreadsAndBlockingCalls() - { - foreach (var type in Enum.GetValues()) - { - foreach (var call in BlockingCalls) - { - yield return new object[] { new Executor(type), call }; - } - } - } - } -} diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTestHelper.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTestHelper.cs deleted file mode 100644 index 52f3b3a8c387ca..00000000000000 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTestHelper.cs +++ /dev/null @@ -1,403 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; -using System.IO; -using System.Runtime.CompilerServices; -using System.Threading.Tasks; -using System.Threading; -using Xunit; -using System.Diagnostics.CodeAnalysis; -using System.Reflection; - -namespace System.Runtime.InteropServices.JavaScript.Tests -{ - public partial class WebWorkerTestHelper - { - public static readonly string LocalHttpEcho = "http://" + Environment.GetEnvironmentVariable("DOTNET_TEST_HTTPHOST") + "/Echo.ashx"; - public static readonly string LocalWsEcho = "ws://" + Environment.GetEnvironmentVariable("DOTNET_TEST_WEBSOCKETHOST") + "/WebSocket/EchoWebSocket.ashx"; - - [JSImport("globalThis.console.log")] - [return: JSMarshalAs] - public static partial void Log(string message); - - [JSImport("delay", "InlineTestHelper")] - public static partial Task JSDelay(int ms); - - [JSImport("getTid", "WebWorkerTestHelper")] - public static partial int GetTid(); - - [JSImport("getState", "WebWorkerTestHelper")] - public static partial JSObject GetState(); - - [JSImport("promiseState", "WebWorkerTestHelper")] - public static partial Task PromiseState(); - - [JSImport("validateState", "WebWorkerTestHelper")] - public static partial bool ValidateState(JSObject state); - - [JSImport("promiseValidateState", "WebWorkerTestHelper")] - public static partial Task PromiseValidateState(JSObject state); - - [JSImport("callMeBackSync", "WebWorkerTestHelper")] - public static partial Task CallMeBackSync([JSMarshalAs] Action syncCallback); - - [JSImport("callExportBackSync", "WebWorkerTestHelper")] - public static partial Task CallExportBackSync(string syncExportName); - - public static NamedCall CurrentCallback; - public static CancellationToken CurrentCancellationToken = CancellationToken.None; - public static Exception? LastException = null; - - [JSExport] - public static void CallCurrentCallback() - { - LastException = null; - try - { - CurrentCallback.Call(CurrentCancellationToken); - } - catch (Exception ex) - { - LastException = ex; - } - } - - public static string GetOriginUrl() - { - using var globalThis = JSHost.GlobalThis; - using var document = globalThis.GetPropertyAsJSObject("document"); - using var location = globalThis.GetPropertyAsJSObject("location"); - return location.GetPropertyAsString("origin"); - } - - public static Task ImportModuleFromString(string jsModule) - { - var es6DataUrl = $"data:text/javascript,{jsModule.Replace('\r', ' ').Replace('\n', ' ')}"; - return JSHost.ImportAsync("InlineTestHelper", es6DataUrl); - } - - #region Setup - - [ThreadStatic] - public static JSObject WebWorkerTestHelperModule; - [ThreadStatic] - public static JSObject InlineTestHelperModule; - - [JSImport("setup", "WebWorkerTestHelper")] - internal static partial Task Setup(); - - [JSImport("INTERNAL.forceDisposeProxies")] - internal static partial void ForceDisposeProxies(bool disposeMethods, bool verbose); - - public static async Task CreateDelay() - { - if (InlineTestHelperModule == null) - { - InlineTestHelperModule = await ImportModuleFromString(@" - export function delay(ms) { - return new Promise(resolve => setTimeout(resolve, ms)) - } - ").ConfigureAwait(false); - } - else - { - await JSDelay(1).ConfigureAwait(false); - } - } - - public static async Task InitializeAsync() - { - if (WebWorkerTestHelperModule != null) - { - await DisposeAsync(); - } - - WebWorkerTestHelperModule = await JSHost.ImportAsync("WebWorkerTestHelper", "../WebWorkerTestHelper.mjs?" + Guid.NewGuid()); - await CreateDelay(); - await Setup(); - } - - public static Task DisposeAsync() - { - WebWorkerTestHelperModule?.Dispose(); - WebWorkerTestHelperModule = null; - return Task.CompletedTask; - } - - [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "thread_id")] - private static extern ref long GetThreadNativeThreadId(Thread @this); - - public static IntPtr NativeThreadId => (int)GetThreadNativeThreadId(Thread.CurrentThread); - - #endregion - } - - #region Executor - - public enum ExecutorType - { - Main, - ThreadPool, - NewThread, - JSWebWorker, - } - - public class Executor - { - public int ExecutorTID; - private static SynchronizationContext _mainSynchronizationContext; - public static SynchronizationContext MainSynchronizationContext - { - - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern")] - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:UnrecognizedReflectionPattern")] - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2060:UnrecognizedReflectionPattern")] - get - { - if (_mainSynchronizationContext != null) - { - return _mainSynchronizationContext; - } - var jsProxyContext = typeof(JSObject).Assembly.GetType("System.Runtime.InteropServices.JavaScript.JSProxyContext"); - var mainThreadContext = jsProxyContext.GetField("_MainThreadContext", BindingFlags.NonPublic | BindingFlags.Static); - var synchronizationContext = jsProxyContext.GetField("SynchronizationContext", BindingFlags.Public | BindingFlags.Instance); - var mainCtx = mainThreadContext.GetValue(null); - _mainSynchronizationContext = (SynchronizationContext)synchronizationContext.GetValue(mainCtx); - return _mainSynchronizationContext; - } - } - - public ExecutorType Type; - - public Executor(ExecutorType type) - { - Type = type; - } - - public Task Execute(Func job, CancellationToken cancellationToken) - { - Task wrapExecute() - { - ExecutorTID = Environment.CurrentManagedThreadId; - AssertTargetThread(); - return job(); - } - - switch (Type) - { - case ExecutorType.Main: - return RunOnTargetAsync(MainSynchronizationContext, wrapExecute, cancellationToken); - case ExecutorType.ThreadPool: - return RunOnThreadPool(wrapExecute, cancellationToken); - case ExecutorType.NewThread: - return RunOnNewThread(wrapExecute, cancellationToken); - case ExecutorType.JSWebWorker: - return JSWebWorker.RunAsync(wrapExecute, cancellationToken); - default: - throw new InvalidOperationException(); - } - } - - public void AssertTargetThread() - { - if (Type == ExecutorType.Main) - { - Assert.Equal(1, Environment.CurrentManagedThreadId); - } - else - { - Assert.NotEqual(1, Environment.CurrentManagedThreadId); - } - if (Type == ExecutorType.ThreadPool) - { - Assert.True(Thread.CurrentThread.IsThreadPoolThread, "IsThreadPoolThread:" + Thread.CurrentThread.IsThreadPoolThread + " Type " + Type); - } - else - { - Assert.False(Thread.CurrentThread.IsThreadPoolThread, "IsThreadPoolThread:" + Thread.CurrentThread.IsThreadPoolThread + " Type " + Type); - } - if (Type == ExecutorType.Main || Type == ExecutorType.JSWebWorker) - { - Assert.NotNull(SynchronizationContext.Current); - Assert.Equal("System.Runtime.InteropServices.JavaScript.JSSynchronizationContext", SynchronizationContext.Current.GetType().FullName); - } - else - { - Assert.Null(SynchronizationContext.Current); - } - } - - public void AssertAwaitCapturedContext() - { - switch (Type) - { - case ExecutorType.Main: - Assert.Equal(1, Environment.CurrentManagedThreadId); - Assert.Equal(ExecutorTID, Environment.CurrentManagedThreadId); - Assert.False(Thread.CurrentThread.IsThreadPoolThread); - break; - case ExecutorType.JSWebWorker: - Assert.NotEqual(1, Environment.CurrentManagedThreadId); - Assert.Equal(ExecutorTID, Environment.CurrentManagedThreadId); - Assert.False(Thread.CurrentThread.IsThreadPoolThread); - break; - case ExecutorType.NewThread: - Assert.NotEqual(1, Environment.CurrentManagedThreadId); - // sometimes this is TP and some times newThread, why ? - if (Thread.CurrentThread.IsThreadPoolThread) - { - Assert.NotEqual(ExecutorTID, Environment.CurrentManagedThreadId); - } - else - { - Assert.Equal(ExecutorTID, Environment.CurrentManagedThreadId); - } - break; - case ExecutorType.ThreadPool: - // it could migrate to any TP thread - Assert.NotEqual(1, Environment.CurrentManagedThreadId); - Assert.True(Thread.CurrentThread.IsThreadPoolThread); - break; - } - } - - public override string ToString() => Type.ToString(); - - // make sure we stay on the executor - public async Task StickyAwait(Task task, CancellationToken cancellationToken) - { - if (Type == ExecutorType.NewThread) - { - task.Wait(cancellationToken); - } - else - { - await task.ConfigureAwait(true); - } - AssertTargetThread(); - } - - public static Task RunOnThreadPool(Func job, CancellationToken cancellationToken) - { - TaskCompletionSource done = new TaskCompletionSource(); - var reg = cancellationToken.Register(() => - { - done.TrySetException(new OperationCanceledException(cancellationToken)); - }); - Task.Run(job, cancellationToken).ContinueWith(result => - { - if (result.IsFaulted) - { - if (result.Exception is AggregateException ag && ag.InnerException != null) - { - done.TrySetException(ag.InnerException); - } - else - { - done.TrySetException(result.Exception); - } - } - else if (result.IsCanceled) - { - done.TrySetCanceled(cancellationToken); - } - else - { - done.TrySetResult(); - } - }, TaskContinuationOptions.ExecuteSynchronously); - return done.Task; - } - - public static Task RunOnNewThread(Func job, CancellationToken cancellationToken) - { - TaskCompletionSource tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var thread = new Thread(() => - { - CancellationTokenRegistration? reg = null; - try - { - if (cancellationToken.IsCancellationRequested) - { - tcs.TrySetException(new OperationCanceledException(cancellationToken)); - return; - } - reg = cancellationToken.Register(() => - { - tcs.TrySetException(new OperationCanceledException(cancellationToken)); - }); - var task = job(); - task.Wait(cancellationToken); - tcs.TrySetResult(); - } - catch (Exception ex) - { - if (ex is AggregateException agg) - { - tcs.TrySetException(agg.InnerException); - } - else - { - tcs.TrySetException(ex); - } - } - finally - { - reg?.Dispose(); - } - }); - thread.Start(); - tcs.Task.ContinueWith((t) => { thread.Join(); }, cancellationToken, TaskContinuationOptions.RunContinuationsAsynchronously, TaskScheduler.Default); - return tcs.Task; - } - - public static Task RunOnTargetAsync(SynchronizationContext ctx, Func job, CancellationToken cancellationToken) - { - TaskCompletionSource tcs = new TaskCompletionSource(); - ctx.Post(async _ => - { - CancellationTokenRegistration? reg = null; - try - { - reg = cancellationToken.Register(() => - { - tcs.TrySetException(new OperationCanceledException(cancellationToken)); - }); - await job().ConfigureAwait(true); - tcs.TrySetResult(); - } - catch (Exception ex) - { - tcs.TrySetException(ex); - } - finally - { - reg?.Dispose(); - } - }, null); - return tcs.Task; - } - } - - #endregion - - public class NamedCall - { - public string Name { get; set; } - public bool IsBlocking { get; set; } - public delegate void Method(CancellationToken ct); - public Method Call { get; set; } - - override public string ToString() => Name; - } - - public class FinalizerTest - { - public static bool FinalizerHit; - ~FinalizerTest() - { - FinalizerHit = true; - } - } -} diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTestHelper.mjs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTestHelper.mjs deleted file mode 100644 index e2a8cfadfaea7e..00000000000000 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTestHelper.mjs +++ /dev/null @@ -1,85 +0,0 @@ -let dllExports; - -let jsState = {}; - -let runtime; - -export async function setup() { - try { - if (!getDotnetRuntime) { - throw new Error("getDotnetRuntime is null or undefined"); - } - if (!runtime) { - runtime = getDotnetRuntime(0); - } - if (!runtime) { - console.error(getDotnetRuntime); - throw new Error("runtime is null or undefined"); - } - dllExports = await runtime.getAssemblyExports("System.Runtime.InteropServices.JavaScript.Tests.dll"); - if (!dllExports) { - throw new Error("dllExports is null or undefined"); - } - jsState.id = getRndInteger(0, 1000); - jsState.tid = getTid(); - } - catch (e) { - console.error("MONO_WASM: WebWorkerTestHelper.setup failed: " + JSON.stringify(globalThis.monoThreadInfo, null, 2)); - console.error("MONO_WASM: WebWorkerTestHelper.setup failed: " + e.toString()); - throw e; - } -} - -export function getState() { - return jsState; -} - -export function validateState(state) { - try { - if (!state) { - throw new Error("state is null or undefined"); - } - const isvalid = state.tid === jsState.tid && state.id === jsState.id; - if (!isvalid) { - console.log("Expected: ", JSON.stringify(jsState)); - console.log("Actual: ", JSON.stringify(state)); - } - return isvalid; - } - catch (e) { - console.error("MONO_WASM: WebWorkerTestHelper.validateState failed: " + e.toString()); - throw e; - } -} - -export async function promiseState() { - await delay(10); - return getState(); -} - -export async function promiseValidateState(state) { - await delay(10); - return validateState(state); -} - -export function getTid() { - return runtime.Module["_pthread_self"](); -} - -export function delay(ms) { - return new Promise(resolve => setTimeout(resolve, ms)) -} - -export function getRndInteger(min, max) { - return Math.floor(Math.random() * (max - min)) + min; -} - -export async function callMeBackSync(syncCallback) { - syncCallback(); -} - -export async function callExportBackSync(syncExportName) { - const WebWorkerTestHelper = dllExports.System.Runtime.InteropServices.JavaScript.Tests.WebWorkerTestHelper; - const method = WebWorkerTestHelper[syncExportName] - method(); -} \ No newline at end of file diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/test.json b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/test.json deleted file mode 100644 index 9c3916c2a43ae9..00000000000000 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/test.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "hello":"world" -} \ No newline at end of file