cancellationToken.ThrowIfCancellationRequested();
JSObject fetchResponse = await BrowserHttpInterop.CancelationHelper(promise, cancellationToken, abortController, null).ConfigureAwait(true);
- return new WasmFetchResponse(fetchResponse, abortRegistration.Value);
+ return new WasmFetchResponse(fetchResponse, abortController, abortRegistration.Value);
}
catch (JSException jse)
{
{
// this would also trigger abort
abortRegistration?.Dispose();
+ abortController?.Dispose();
throw;
}
}
public readonly object ThisLock = new object();
#endif
public JSObject? FetchResponse;
+ private readonly JSObject _abortController;
private readonly CancellationTokenRegistration _abortRegistration;
private bool _isDisposed;
- public WasmFetchResponse(JSObject fetchResponse, CancellationTokenRegistration abortRegistration)
+ public WasmFetchResponse(JSObject fetchResponse, JSObject abortController, CancellationTokenRegistration abortRegistration)
{
ArgumentNullException.ThrowIfNull(fetchResponse);
+ ArgumentNullException.ThrowIfNull(abortController);
FetchResponse = fetchResponse;
_abortRegistration = abortRegistration;
+ _abortController = abortController;
}
public void ThrowIfDisposed()
return;
self._isDisposed = true;
self._abortRegistration.Dispose();
+ self._abortController.Dispose();
if (!self.FetchResponse!.IsDisposed)
{
BrowserHttpInterop.AbortResponse(self.FetchResponse);
#else
_isDisposed = true;
_abortRegistration.Dispose();
+ _abortController.Dispose();
if (FetchResponse != null)
{
if (!FetchResponse.IsDisposed)
}
[Fact]
+ [SkipOnPlatform(TestPlatforms.Browser, "Browser is relaxed about validating HTTP headers")]
public async Task FailedRequests_ConnectionClosedWhileReceivingHeaders_Recorded()
{
using CancellationTokenSource cancelServerCts = new CancellationTokenSource();
public sealed class JSException : Exception
{
internal JSObject? jsException;
+ internal string? combinedStackTrace;
/// <summary>
/// Initializes a new instance of the JSException class with a specified error message.
public JSException(string msg) : base(msg)
{
jsException = null;
+ combinedStackTrace = null;
}
internal JSException(string msg, JSObject? jsException) : base(msg)
{
this.jsException = jsException;
+ this.combinedStackTrace = null;
}
/// <inheritdoc />
{
get
{
+ if (combinedStackTrace != null)
+ {
+ return combinedStackTrace;
+ }
var bs = base.StackTrace;
if (jsException == null)
{
#if FEATURE_WASM_THREADS
if (jsException.OwnerThreadId != Thread.CurrentThread.ManagedThreadId)
{
- return null;
+ return bs;
}
#endif
string? jsStackTrace = jsException.GetPropertyAsString("stack");
+
+ // after this, we don't need jsException proxy anymore
+ jsException.Dispose();
+ jsException = null;
+
if (jsStackTrace == null)
{
- if (bs == null)
- {
- return null;
- }
+ combinedStackTrace = bs;
+ return combinedStackTrace;
}
else if (jsStackTrace.StartsWith(Message + "\n"))
{
if (bs == null)
{
- return jsStackTrace;
+ combinedStackTrace = jsStackTrace;
}
- return base.StackTrace + "\r\n" + jsStackTrace;
- }
+ combinedStackTrace = bs != null
+ ? bs + Environment.NewLine + jsStackTrace
+ : jsStackTrace;
+
+ return combinedStackTrace;
+ }
}
/// <inheritdoc />
jso.Dispose();
}
}
- foreach (var gch in ThreadJsOwnedObjects.Values)
- {
- GCHandle gcHandle = (GCHandle)gch;
-
- // if this is pending promise we reject it
- if (gcHandle.Target is TaskCallback holder)
- {
- unsafe
- {
- holder.Callback!.Invoke(null);
- }
- }
- gcHandle.Free();
- }
SynchronizationContext.SetSynchronizationContext(ctx!.previousSynchronizationContext);
JSSynchronizationContext.CurrentJSSynchronizationContext = null;
ctx.isDisposed = true;
Environment.FailFast($"There should be no JS proxies of managed objects on this thread, ManagedThreadId: {Thread.CurrentThread.ManagedThreadId}");
}
}
+
+ Interop.Runtime.UninstallWebWorkerInterop(uninstallJSSynchronizationContext);
+
+ if (uninstallJSSynchronizationContext)
+ {
+ try
+ {
+ foreach (var gch in ThreadJsOwnedObjects.Values)
+ {
+ GCHandle gcHandle = (GCHandle)gch;
+
+ // if this is pending promise we reject it
+ if (gcHandle.Target is TaskCallback holder)
+ {
+ unsafe
+ {
+ holder.Callback!.Invoke(null);
+ }
+ }
+ gcHandle.Free();
+ }
+ }
+ catch(Exception ex)
+ {
+ Environment.FailFast($"Unexpected error in UninstallWebWorkerInterop, ManagedThreadId: {Thread.CurrentThread.ManagedThreadId}. " + ex);
+ }
+ }
+
ThreadCsOwnedObjects.Clear();
ThreadJsOwnedObjects.Clear();
- Interop.Runtime.UninstallWebWorkerInterop(uninstallJSSynchronizationContext);
}
private static FieldInfo? thread_id_Field;
/// <reference path="./types/v8.d.ts" />
/// <reference path="./types/node.d.ts" />
-import { mono_log_error } from "./logging";
import { RuntimeAPI } from "./types/index";
import type { GlobalObjects, EmscriptenInternals, RuntimeHelpers, LoaderHelpers, DotnetModuleInternal, PromiseAndController } from "./types/internal";
const message = "Assert failed: " + (typeof messageFactory === "function"
? messageFactory()
: messageFactory);
- const abort = runtimeHelpers.mono_wasm_abort;
- if (abort) {
- mono_log_error(message);
- abort();
- }
- throw new Error(message);
+ const error = new Error(message);
+ runtimeHelpers.abort(error);
}
\ No newline at end of file
import { assert_runtime_running, is_exited, is_runtime_running, mono_exit } from "./exit";
import { assertIsControllablePromise, createPromiseController, getPromiseController } from "./promise-controller";
import { mono_download_assets, resolve_asset_path } from "./assets";
-import { mono_log_error, setup_proxy_console } from "./logging";
+import { setup_proxy_console } from "./logging";
import { hasDebuggingEnabled } from "./blazor/_Polyfill";
import { invokeLibraryInitializers } from "./libraryInitializers";
export const ENVIRONMENT_IS_WORKER = typeof importScripts == "function";
export const ENVIRONMENT_IS_SHELL = !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_WORKER;
-export let runtimeHelpers: RuntimeHelpers = null as any;
-export let loaderHelpers: LoaderHelpers = null as any;
-export let exportedRuntimeAPI: RuntimeAPI = null as any;
-export let INTERNAL: any;
+export let runtimeHelpers: RuntimeHelpers = {} as any;
+export let loaderHelpers: LoaderHelpers = {} as any;
+export let exportedRuntimeAPI: RuntimeAPI = {} as any;
+export let INTERNAL: any = {};
export let _loaderModuleLoaded = false; // please keep it in place also as rollup guard
export const globalObjectsRoot: GlobalObjects = {
mono: {},
binding: {},
- internal: {},
+ internal: INTERNAL,
module: {},
- loaderHelpers: {},
- runtimeHelpers: {},
- api: {}
+ loaderHelpers,
+ runtimeHelpers,
+ api: exportedRuntimeAPI,
} as any;
setLoaderGlobals(globalObjectsRoot);
mono_wasm_bindings_is_ready: false,
javaScriptExports: {} as any,
config: globalObjects.module.config,
- diagnosticTracing: false
+ diagnosticTracing: false,
+ abort: (reason: any) => { throw reason; },
});
Object.assign(loaderHelpers, {
config: globalObjects.module.config,
const message = "Assert failed: " + (typeof messageFactory === "function"
? messageFactory()
: messageFactory);
- const abort = globalObjectsRoot.runtimeHelpers.mono_wasm_abort;
- if (abort) {
- mono_log_error(message);
- abort();
- }
- throw new Error(message);
+ const error = new Error(message);
+ runtimeHelpers.abort(error);
}
\ No newline at end of file
import BuildConfiguration from "consts:configuration";
import cwraps from "./cwraps";
-import { _lookup_js_owned_object, mono_wasm_get_jsobj_from_js_handle, mono_wasm_get_js_handle, setup_managed_proxy } from "./gc-handles";
+import { _lookup_js_owned_object, mono_wasm_get_jsobj_from_js_handle, mono_wasm_get_js_handle, setup_managed_proxy, teardown_managed_proxy } from "./gc-handles";
import { Module, createPromiseController, loaderHelpers, mono_assert, runtimeHelpers } from "./globals";
import {
ManagedObject, ManagedError,
return runtimeHelpers.javaScriptExports.call_delegate(gc_handle, arg1_js, arg2_js, arg3_js, res_converter, arg1_converter, arg2_converter, arg3_converter);
};
result.dispose = () => {
- result.isDisposed = true;
+ if (!result.isDisposed) {
+ result.isDisposed = true;
+ teardown_managed_proxy(result, gc_handle);
+ }
};
result.isDisposed = false;
if (BuildConfiguration === "Debug") {
init_c_exports();
runtimeHelpers.mono_wasm_exit = cwraps.mono_wasm_exit;
- runtimeHelpers.mono_wasm_abort = cwraps.mono_wasm_abort;
+ runtimeHelpers.abort = (reason: any) => {
+ if (!loaderHelpers.is_exited()) {
+ cwraps.mono_wasm_abort();
+ }
+ throw reason;
+ };
cwraps_internal(INTERNAL);
if (WasmEnableLegacyJsInterop && !linkerDisableLegacyJsInterop) {
cwraps_mono_api(MONO);
ExitStatus: ExitStatusError;
quit: Function,
mono_wasm_exit?: (code: number) => void,
- mono_wasm_abort?: () => void,
+ abort: (reason: any) => void,
javaScriptExports: JavaScriptExports,
storeMemorySnapshotPending: boolean,
memorySnapshotCacheKey: string,
import { PromiseController } from "./types/internal";
import { mono_log_warn } from "./logging";
import { viewOrCopy, utf8ToStringRelaxed, stringToUTF8 } from "./strings";
+import { IDisposable } from "./marshal";
const wasm_ws_pending_send_buffer = Symbol.for("wasm ws_pending_send_buffer");
const wasm_ws_pending_send_buffer_offset = Symbol.for("wasm ws_pending_send_buffer_offset");
const wasm_ws_pending_close_promises = Symbol.for("wasm ws_pending_close_promises");
const wasm_ws_pending_send_promises = Symbol.for("wasm ws_pending_send_promises");
const wasm_ws_is_aborted = Symbol.for("wasm ws_is_aborted");
+const wasm_ws_on_closed = Symbol.for("wasm ws_on_closed");
const wasm_ws_receive_status_ptr = Symbol.for("wasm ws_receive_status_ptr");
let mono_wasm_web_socket_close_warning = false;
const ws_send_buffer_blocking_threshold = 65536;
export function ws_wasm_create(uri: string, sub_protocols: string[] | null, receive_status_ptr: VoidPtr, onClosed: (code: number, reason: string) => void): WebSocketExtension {
verifyEnvironment();
mono_assert(uri && typeof uri === "string", () => `ERR12: Invalid uri ${typeof uri}`);
+ mono_assert(typeof onClosed === "function", () => `ERR12: Invalid onClosed ${typeof onClosed}`);
const ws = new globalThis.WebSocket(uri, sub_protocols || undefined) as WebSocketExtension;
const { promise_control: open_promise_control } = createPromiseController<WebSocketExtension>();
ws[wasm_ws_pending_send_promises] = [];
ws[wasm_ws_pending_close_promises] = [];
ws[wasm_ws_receive_status_ptr] = receive_status_ptr;
+ ws[wasm_ws_on_closed] = onClosed as any;
ws.binaryType = "arraybuffer";
const local_on_open = () => {
if (ws[wasm_ws_is_aborted]) return;
const local_on_close = (ev: CloseEvent) => {
ws.removeEventListener("message", local_on_message);
if (ws[wasm_ws_is_aborted]) return;
- if (onClosed) onClosed(ev.code, ev.reason);
+
+ onClosed(ev.code, ev.reason);
// this reject would not do anything if there was already "open" before it.
- open_promise_control.reject(ev.reason);
+ open_promise_control.reject(new Error(ev.reason));
for (const close_promise_control of ws[wasm_ws_pending_close_promises]) {
close_promise_control.resolve();
setI32(<any>receive_status_ptr + 8, 1);// end_of_message: true
receive_promise_control.resolve();
});
+
+ // cleanup the delegate proxy
+ ws[wasm_ws_on_closed].dispose();
};
const local_on_error = (ev: any) => {
- open_promise_control.reject(ev.message || "WebSocket error");
+ if (ws[wasm_ws_is_aborted]) return;
+ ws.removeEventListener("message", local_on_message);
+ const error = new Error(ev.message || "WebSocket error");
+ mono_log_warn("WebSocket error", error);
+ reject_promises(ws, error);
};
ws.addEventListener("message", local_on_message);
ws.addEventListener("open", local_on_open, { once: true });
mono_assert(!!ws, "ERR18: expected ws instance");
ws[wasm_ws_is_aborted] = true;
+ reject_promises(ws, new Error("OperationCanceledException"));
+
+ // cleanup the delegate proxy
+ ws[wasm_ws_on_closed]?.dispose();
+
+ // this is different from Managed implementation
+ ws.close(1000, "Connection was aborted.");
+}
+
+function reject_promises(ws: WebSocketExtension, error: Error) {
const open_promise_control = ws[wasm_ws_pending_open_promise];
if (open_promise_control) {
- open_promise_control.reject(new Error("OperationCanceledException"));
+ open_promise_control.reject(error);
}
for (const close_promise_control of ws[wasm_ws_pending_close_promises]) {
- close_promise_control.reject(new Error("OperationCanceledException"));
+ close_promise_control.reject(error);
}
for (const send_promise_control of ws[wasm_ws_pending_send_promises]) {
- send_promise_control.reject(new Error("OperationCanceledException"));
+ send_promise_control.reject(error);
}
ws[wasm_ws_pending_receive_promise_queue].drain(receive_promise_control => {
- receive_promise_control.reject(new Error("OperationCanceledException"));
+ receive_promise_control.reject(error);
});
-
- // this is different from Managed implementation
- ws.close(1000, "Connection was aborted.");
}
// send and return promise
if (readyState != WebSocket.OPEN && readyState != WebSocket.CLOSING) {
// only reject if the data were not sent
// bufferedAmount does not reset to zero once the connection closes
- promise_control.reject(`InvalidState: ${readyState} The WebSocket is not connected.`);
+ promise_control.reject(new Error(`InvalidState: ${readyState} The WebSocket is not connected.`));
}
else if (!promise_control.isDone) {
globalThis.setTimeout(polling_check, nextDelay);
[wasm_ws_pending_send_promises]: PromiseController<void>[]
[wasm_ws_pending_close_promises]: PromiseController<void>[]
[wasm_ws_is_aborted]: boolean
+ [wasm_ws_on_closed]: IDisposable
[wasm_ws_receive_status_ptr]: VoidPtr
[wasm_ws_pending_send_buffer_offset]: number
[wasm_ws_pending_send_buffer_type]: number