FastState = WebSocketState.Aborted;
throw new OperationCanceledException(cancellationToken);
}
- if (ex.Message == "OperationCanceledException")
+ if (ex.Message == "Error: OperationCanceledException")
{
FastState = WebSocketState.Aborted;
throw new OperationCanceledException("The operation was cancelled.", ex, cancellationToken);
{
GCHandle handle = (GCHandle)arg_1.slot.GCHandle;
- lock (JSHostImplementation.s_gcHandleFromJSOwnedObject)
- {
- JSHostImplementation.s_gcHandleFromJSOwnedObject.Remove(handle.Target!);
- handle.Free();
- }
+ JSHostImplementation.ThreadJsOwnedObjects.Remove(handle.Target!);
+ handle.Free();
}
catch (Exception ex)
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static unsafe void InvokeJSImpl(JSObject jsFunction, Span<JSMarshalerArgument> arguments)
{
+ ObjectDisposedException.ThrowIf(jsFunction.IsDisposed, jsFunction);
#if FEATURE_WASM_THREADS
JSObject.AssertThreadAffinity(jsFunction);
#endif
internal static unsafe JSFunctionBinding BindJSFunctionImpl(string functionName, string moduleName, ReadOnlySpan<JSMarshalerType> signatures)
{
#if FEATURE_WASM_THREADS
- if (JSSynchronizationContext.CurrentJSSynchronizationContext == null)
- {
- throw new InvalidOperationException("Please use dedicated worker for working with JavaScript interop. See https://github.com/dotnet/runtime/blob/main/src/mono/wasm/threads.md#JS-interop-on-dedicated-threads ");
- }
+ JSSynchronizationContext.AssertWebWorkerContext();
#endif
var signature = JSHostImplementation.GetMethodSignature(signatures);
{
get
{
+#if FEATURE_WASM_THREADS
+ JSSynchronizationContext.AssertWebWorkerContext();
+#endif
return JavaScriptImports.GetGlobalThis();
}
}
{
get
{
+#if FEATURE_WASM_THREADS
+ JSSynchronizationContext.AssertWebWorkerContext();
+#endif
return JavaScriptImports.GetDotnetInstance();
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Task<JSObject> ImportAsync(string moduleName, string moduleUrl, CancellationToken cancellationToken = default)
{
+#if FEATURE_WASM_THREADS
+ JSSynchronizationContext.AssertWebWorkerContext();
+#endif
return JSHostImplementation.ImportAsync(moduleName, moduleUrl, cancellationToken);
}
}
// we use this to maintain identity of GCHandle for a managed object
- public static Dictionary<object, IntPtr> s_gcHandleFromJSOwnedObject = new Dictionary<object, IntPtr>(ReferenceEqualityComparer.Instance);
+#if FEATURE_WASM_THREADS
+ [ThreadStatic]
+#endif
+ private static Dictionary<object, IntPtr>? s_jsOwnedObjects;
+
+ public static Dictionary<object, IntPtr> ThreadJsOwnedObjects
+ {
+ get
+ {
+ s_jsOwnedObjects ??= new Dictionary<object, IntPtr>(ReferenceEqualityComparer.Instance);
+ return s_jsOwnedObjects;
+ }
+ }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReleaseCSOwnedObject(nint jsHandle)
{
if (jsHandle != IntPtr.Zero)
{
+#if FEATURE_WASM_THREADS
+ JSSynchronizationContext.AssertWebWorkerContext();
+#endif
ThreadCsOwnedObjects.Remove((int)jsHandle);
Interop.Runtime.ReleaseCSOwnedObject(jsHandle);
}
public static IntPtr GetJSOwnedObjectGCHandle(object obj, GCHandleType handleType = GCHandleType.Normal)
{
if (obj == null)
+ {
return IntPtr.Zero;
+ }
- IntPtr result;
- lock (s_gcHandleFromJSOwnedObject)
+ IntPtr gcHandle;
+ if (ThreadJsOwnedObjects.TryGetValue(obj, out gcHandle))
{
- IntPtr gcHandle;
- if (s_gcHandleFromJSOwnedObject.TryGetValue(obj, out gcHandle))
- return gcHandle;
-
- result = (IntPtr)GCHandle.Alloc(obj, handleType);
- s_gcHandleFromJSOwnedObject[obj] = result;
- return result;
+ return gcHandle;
}
+
+ IntPtr result = (IntPtr)GCHandle.Alloc(obj, handleType);
+ ThreadJsOwnedObjects[obj] = result;
+ return result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static JSObject CreateCSOwnedProxy(nint jsHandle)
{
+#if FEATURE_WASM_THREADS
+ JSSynchronizationContext.AssertWebWorkerContext();
+#endif
JSObject? res;
if (!ThreadCsOwnedObjects.TryGetValue((int)jsHandle, out WeakReference<JSObject>? reference) ||
public static void UninstallWebWorkerInterop()
{
- var ctx = SynchronizationContext.Current as JSSynchronizationContext;
+ var ctx = JSSynchronizationContext.CurrentJSSynchronizationContext;
var uninstallJSSynchronizationContext = ctx != null;
if (uninstallJSSynchronizationContext)
{
- SynchronizationContext.SetSynchronizationContext(ctx!.previousSynchronizationContext);
- JSSynchronizationContext.CurrentJSSynchronizationContext = null;
- ctx.isDisposed = true;
+ try
+ {
+ foreach (var jsObjectWeak in ThreadCsOwnedObjects.Values)
+ {
+ if (jsObjectWeak.TryGetTarget(out var jso))
+ {
+ 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;
+ }
+ catch(Exception ex)
+ {
+ Environment.FailFast($"Unexpected error in UninstallWebWorkerInterop, ManagedThreadId: {Thread.CurrentThread.ManagedThreadId}. " + ex);
+ }
+ }
+ else
+ {
+ if (ThreadCsOwnedObjects.Count > 0)
+ {
+ Environment.FailFast($"There should be no JSObjects proxies on this thread, ManagedThreadId: {Thread.CurrentThread.ManagedThreadId}");
+ }
+ if (ThreadJsOwnedObjects.Count > 0)
+ {
+ Environment.FailFast($"There should be no JS proxies of managed objects on this thread, ManagedThreadId: {Thread.CurrentThread.ManagedThreadId}");
+ }
}
+ ThreadCsOwnedObjects.Clear();
+ ThreadJsOwnedObjects.Clear();
Interop.Runtime.UninstallWebWorkerInterop(uninstallJSSynchronizationContext);
}
public bool HasProperty(string propertyName)
{
ObjectDisposedException.ThrowIf(IsDisposed, this);
+#if FEATURE_WASM_THREADS
+ JSObject.AssertThreadAffinity(this);
+#endif
return JavaScriptImports.HasProperty(this, propertyName);
}
{
}
+ internal static void AssertWebWorkerContext()
+ {
+#if FEATURE_WASM_THREADS
+ if (CurrentJSSynchronizationContext == null)
+ {
+ throw new InvalidOperationException("Please use dedicated worker for working with JavaScript interop. See https://aka.ms/dotnet-JS-interop-threads");
+ }
+#endif
+ }
+
private JSSynchronizationContext(Thread targetThread, IntPtr targetThreadId, WorkItemQueueType queue)
{
TargetThread = targetThread;
TaskCompletionSource tcs = new TaskCompletionSource(holder);
JSHostImplementation.ToManagedCallback callback = (JSMarshalerArgument* arguments_buffer) =>
{
+ if (arguments_buffer == null)
+ {
+ tcs.TrySetException(new TaskCanceledException("WebWorker which is origin of the Promise is being terminated."));
+ return;
+ }
ref JSMarshalerArgument arg_2 = ref arguments_buffer[3]; // set by caller when this is SetException call
// arg_3 set by caller when this is SetResult call, un-used here
if (arg_2.slot.Type != MarshalerType.None)
TaskCompletionSource<T> tcs = new TaskCompletionSource<T>(holder);
JSHostImplementation.ToManagedCallback callback = (JSMarshalerArgument* arguments_buffer) =>
{
+ if (arguments_buffer == null)
+ {
+ tcs.TrySetException(new TaskCanceledException("WebWorker which is origin of the Promise is being terminated."));
+ return;
+ }
+
ref JSMarshalerArgument arg_2 = ref arguments_buffer[3]; // set by caller when this is SetException call
ref JSMarshalerArgument arg_3 = ref arguments_buffer[4]; // set by caller when this is SetResult call
if (arg_2.slot.Type != MarshalerType.None)
namespace System.Runtime.InteropServices.JavaScript
{
/// <summary>
- /// This is draft for possible public API of SynchronizationContext
+ /// Extensions of SynchronizationContext which propagate errors and return values
/// </summary>
public static class SynchronizationContextExtension
{
// the continuation is executed by setTimeout() callback of the thread.
res.ContinueWith(t =>
{
- PostWhenDone(parentContext, tcs, res);
+ SendWhenDone(parentContext, tcs, res);
JSHostImplementation.UninstallWebWorkerInterop();
}, childScheduler);
}
catch (Exception ex)
{
- PostWhenException(parentContext, tcs, ex);
+ SendWhenException(parentContext, tcs, ex);
}
});
// the continuation is executed by setTimeout() callback of the thread.
res.ContinueWith(t =>
{
- PostWhenDone(parentContext, tcs, res);
+ SendWhenDone(parentContext, tcs, res);
JSHostImplementation.UninstallWebWorkerInterop();
}, childScheduler);
}
catch (Exception ex)
{
- PostWhenException(parentContext, tcs, ex);
+ SendWhenException(parentContext, tcs, ex);
}
});
try
{
body();
- PostWhenDone(parentContext, tcs);
+ SendWhenDone(parentContext, tcs);
}
catch (Exception ex)
{
- PostWhenException(parentContext, tcs, ex);
+ SendWhenException(parentContext, tcs, ex);
}
JSHostImplementation.UninstallWebWorkerInterop();
}
catch (Exception ex)
{
- PostWhenException(parentContext, tcs, ex);
+ SendWhenException(parentContext, tcs, ex);
}
});
{
try
{
- ctx.Post((_) => tcs.SetCanceled(), null);
+ ctx.Send((_) => tcs.SetCanceled(), null);
}
catch (Exception e)
{
{
try
{
- ctx.Post((_) => tcs.SetCanceled(), null);
+ ctx.Send((_) => tcs.SetCanceled(), null);
}
catch (Exception e)
{
}
}
- private static void PostWhenDone(SynchronizationContext ctx, TaskCompletionSource tcs, Task done)
+ private static void SendWhenDone(SynchronizationContext ctx, TaskCompletionSource tcs, Task done)
{
try
{
- ctx.Post((_) =>
+ ctx.Send((_) =>
{
PropagateCompletion(tcs, done);
}, null);
}
}
- private static void PostWhenDone(SynchronizationContext ctx, TaskCompletionSource tcs)
+ private static void SendWhenDone(SynchronizationContext ctx, TaskCompletionSource tcs)
{
try
{
- ctx.Post((_) => tcs.SetResult(), null);
+ ctx.Send((_) => tcs.SetResult(), null);
}
catch (Exception e)
{
}
}
- private static void PostWhenException(SynchronizationContext ctx, TaskCompletionSource tcs, Exception ex)
+ private static void SendWhenException(SynchronizationContext ctx, TaskCompletionSource tcs, Exception ex)
{
try
{
- ctx.Post((_) => tcs.SetException(ex), null);
+ ctx.Send((_) => tcs.SetException(ex), null);
}
catch (Exception e)
{
}
}
- private static void PostWhenException<T>(SynchronizationContext ctx, TaskCompletionSource<T> tcs, Exception ex)
+ private static void SendWhenException<T>(SynchronizationContext ctx, TaskCompletionSource<T> tcs, Exception ex)
{
try
{
- ctx.Post((_) => tcs.SetException(ex), null);
+ ctx.Send((_) => tcs.SetException(ex), null);
}
catch (Exception e)
{
}
}
- private static void PostWhenDone<T>(SynchronizationContext ctx, TaskCompletionSource<T> tcs, Task<T> done)
+ private static void SendWhenDone<T>(SynchronizationContext ctx, TaskCompletionSource<T> tcs, Task<T> done)
{
try
{
- ctx.Post((_) =>
+ ctx.Send((_) =>
{
PropagateCompletion(tcs, done);
}, null);
var instance1 = first.GetPropertyAsJSObject("instance");
var instance2 = second.GetPropertyAsJSObject("instance");
Assert.Same(instance1, instance2);
+ first.Dispose();
+ second.Dispose();
+ instance1.Dispose();
}
[Fact]
var exTask = Assert.ThrowsAsync<JSException>(async () => await JSHost.ImportAsync("JavaScriptTestHelper", "../JavaScriptTestHelper.mjs", cts.Token));
cts.Cancel();
var actualEx2 = await exTask;
- Assert.Equal("OperationCanceledException", actualEx2.Message);
+ Assert.Equal("Error: OperationCanceledException", actualEx2.Message);
var actualEx = await Assert.ThrowsAsync<JSException>(async () => await JSHost.ImportAsync("JavaScriptTestHelper", "../JavaScriptTestHelper.mjs", new CancellationToken(true)));
- Assert.Equal("OperationCanceledException", actualEx.Message);
+ Assert.Equal("Error: OperationCanceledException", actualEx.Message);
}
[Fact]
public unsafe void GlobalThis()
{
- Assert.Null(JSHost.GlobalThis.GetPropertyAsString("dummy"));
- Assert.False(JSHost.GlobalThis.HasProperty("dummy"));
- Assert.Equal("undefined", JSHost.GlobalThis.GetTypeOfProperty("dummy"));
- Assert.Equal("function", JSHost.GlobalThis.GetTypeOfProperty("Array"));
- Assert.NotNull(JSHost.GlobalThis.GetPropertyAsJSObject("javaScriptTestHelper"));
+ var globalThis = JSHost.GlobalThis;
+ Assert.Null(globalThis.GetPropertyAsString("dummy"));
+ Assert.False(globalThis.HasProperty("dummy"));
+ Assert.Equal("undefined", globalThis.GetTypeOfProperty("dummy"));
+ Assert.Equal("function", globalThis.GetTypeOfProperty("Array"));
+ var javaScriptTestHelper = globalThis.GetPropertyAsJSObject("javaScriptTestHelper");
+ Assert.NotNull(javaScriptTestHelper);
+ globalThis.Dispose();
+ javaScriptTestHelper.Dispose();
}
[Fact]
Assert.Equal(expected, actual);
if (expected != null) for (int i = 0; i < expected.Length; i++)
- {
- var actualI = JavaScriptTestHelper.store_ObjectArray(expected, i);
- Assert.Equal(expected[i], actualI);
- }
+ {
+ var actualI = JavaScriptTestHelper.store_ObjectArray(expected, i);
+ Assert.Equal(expected[i], actualI);
+ }
}
public static IEnumerable<object[]> MarshalObjectArrayCasesToDouble()
await JavaScriptTestHelper.InitializeAsync();
}
- public Task DisposeAsync() => Task.CompletedTask;
+ public async Task DisposeAsync()
+ {
+ await JavaScriptTestHelper.DisposeAsync();
+ }
// js Date doesn't have nanosecond precision
public static DateTime TrimNano(DateTime date)
[JSImport("setup", "JavaScriptTestHelper")]
internal static partial Task Setup();
+ [JSImport("INTERNAL.forceDisposeProxies")]
+ internal static partial void ForceDisposeProxies(bool disposeMethods, bool verbose);
+
static JSObject _module;
public static async Task InitializeAsync()
{
// Log("JavaScriptTestHelper.mjs imported");
}
}
+
+ public static Task DisposeAsync()
+ {
+ _module.Dispose();
+ _module = null;
+ // you can set verbose: true to see which proxies are left to the GC to collect
+ ForceDisposeProxies(false, verbose: false);
+ return Task.CompletedTask;
+ }
}
}
let w0 = await exports.Sample.Test.WsClientMain("wss://corefx-net-http11.azurewebsites.net/WebSocket/EchoWebSocket.ashx");
console.log("smoke: WsClientMain done " + w0);
- /* ActiveIssue https://github.com/dotnet/runtime/issues/88057
console.log("smoke: running FetchBackground(blurst.txt)");
let s = await exports.Sample.Test.FetchBackground(resolveUrl("./blurst.txt"));
console.log("smoke: FetchBackground(blurst.txt) done");
const msg = `Unexpected FetchBackground(missing) result ${s}`;
document.getElementById("out").innerHTML = msg;
throw new Error(msg);
- }*/
+ }
console.log("smoke: running TaskRunCompute");
const r1 = await exports.Sample.Test.RunBackgroundTaskRunCompute();
mono_assert(!!promise, () => `Expected Promise for GCHandle ${task_holder_gc_handle}`);
loaderHelpers.assertIsControllablePromise(promise);
const promise_control = loaderHelpers.getPromiseController(promise);
- promise_control.reject("OperationCanceledException");
+ promise_control.reject(new Error("OperationCanceledException"));
}
import { mono_wasm_gc_lock, mono_wasm_gc_unlock } from "./gc-lock";
import { loadLazyAssembly } from "./lazyLoading";
import { loadSatelliteAssemblies } from "./satelliteAssemblies";
+import { forceDisposeProxies } from "./gc-handles";
export function export_internal(): any {
return {
// tests
mono_wasm_exit: (exit_code: number) => { Module.err("early exit " + exit_code); },
+ forceDisposeProxies,
// with mono_wasm_debugger_log and mono_wasm_trace_logger
logging: undefined,
import { mono_log_warn, mono_wasm_stringify_as_error_with_stack } from "./logging";
import { instantiate_asset, instantiate_symbols_asset } from "./assets";
import { jiterpreter_dump_stats } from "./jiterpreter";
+import { forceDisposeProxies } from "./gc-handles";
function initializeExports(globalObjects: GlobalObjects): RuntimeAPI {
const module = Module;
instantiate_symbols_asset,
instantiate_asset,
jiterpreter_dump_stats,
+ forceDisposeProxies,
});
const API = export_api();
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-import { loaderHelpers, runtimeHelpers } from "./globals";
-import { mono_log_warn } from "./logging";
+import MonoWasmThreads from "consts:monoWasmThreads";
+import BuildConfiguration from "consts:configuration";
+
+import { loaderHelpers, mono_assert, runtimeHelpers } from "./globals";
+import { fn_wrapper_by_fn_handle } from "./invoke-js";
+import { mono_log_info, mono_log_warn } from "./logging";
+import { bound_cs_function_symbol, imported_js_function_symbol, proxy_debug_symbol } from "./marshal";
import { GCHandle, GCHandleNull, JSHandle, JSHandleDisposed, JSHandleNull } from "./types/internal";
-import { create_weak_ref } from "./weak-ref";
+import { _use_weak_ref, create_weak_ref } from "./weak-ref";
+import { exportsByAssembly } from "./invoke-cs";
const _use_finalization_registry = typeof globalThis.FinalizationRegistry === "function";
let _js_owned_object_registry: FinalizationRegistry<any>;
// this is array, not map. We maintain list of gaps in _js_handle_free_list so that it could be as compact as possible
-const _cs_owned_objects_by_js_handle: any[] = [];
+// 0th element is always null, because JSHandle == 0 is invalid handle.
+const _cs_owned_objects_by_js_handle: any[] = [null];
const _js_handle_free_list: JSHandle[] = [];
let _next_js_handle = 1;
export const js_owned_gc_handle_symbol = Symbol.for("wasm js_owned_gc_handle");
export const cs_owned_js_handle_symbol = Symbol.for("wasm cs_owned_js_handle");
+export const do_not_force_dispose = Symbol.for("wasm do_not_force_dispose");
export function mono_wasm_get_jsobj_from_js_handle(js_handle: JSHandle): any {
export function mono_wasm_release_cs_owned_object(js_handle: JSHandle): void {
const obj = _cs_owned_objects_by_js_handle[<any>js_handle];
if (typeof obj !== "undefined" && obj !== null) {
- // if this is the global object then do not
- // unregister it.
- if (globalThis === obj)
- return;
-
if (typeof obj[cs_owned_js_handle_symbol] !== "undefined") {
obj[cs_owned_js_handle_symbol] = undefined;
}
return null;
}
-export function forceDisposeProxies(dump: boolean): void {
+export function assertNoProxies(): void {
+ if (!MonoWasmThreads) return;
+ mono_assert(_js_owned_object_table.size === 0, "There should be no proxies on this thread.");
+ mono_assert(_cs_owned_objects_by_js_handle.length === 1, "There should be no proxies on this thread.");
+ mono_assert(exportsByAssembly.size === 0, "There should be no exports on this thread.");
+ mono_assert(fn_wrapper_by_fn_handle.length === 1, "There should be no imports on this thread.");
+}
+
+// when we arrive here, the C# side is already done with the object.
+// We don't have to call back to release them.
+export function forceDisposeProxies(disposeMethods: boolean, verbose: boolean): void {
+ let keepSomeCsAlive = false;
+ let keepSomeJsAlive = false;
+
+ let doneImports = 0;
+ let doneExports = 0;
+ let doneGCHandles = 0;
+ let doneJSHandles = 0;
// dispose all proxies to C# objects
- const gchandles = [..._js_owned_object_table.keys()];
- for (const gchandle of gchandles) {
- const wr = _js_owned_object_table.get(gchandle);
+ const gc_handles = [..._js_owned_object_table.keys()];
+ for (const gc_handle of gc_handles) {
+ const wr = _js_owned_object_table.get(gc_handle);
const obj = wr.deref();
+ if (_use_finalization_registry && obj) {
+ _js_owned_object_registry.unregister(obj);
+ }
+
if (obj) {
- if (dump) {
- mono_log_warn(`Proxy of C# object with GCHandle ${gchandle} was still alive`);
+ const keepAlive = typeof obj[do_not_force_dispose] === "boolean" && obj[do_not_force_dispose];
+ if (verbose) {
+ const proxy_debug = BuildConfiguration === "Debug" ? obj[proxy_debug_symbol] : undefined;
+ if (BuildConfiguration === "Debug" && proxy_debug) {
+ mono_log_warn(`${proxy_debug} ${typeof obj} was still alive. ${keepAlive ? "keeping" : "disposing"}.`);
+ } else {
+ mono_log_warn(`Proxy of C# ${typeof obj} with GCHandle ${gc_handle} was still alive. ${keepAlive ? "keeping" : "disposing"}.`);
+ }
+ }
+ if (!keepAlive) {
+ const promise_control = loaderHelpers.getPromiseController(obj);
+ if (promise_control) {
+ promise_control.reject(new Error("WebWorker which is origin of the Task is being terminated."));
+ }
+ if (typeof obj.dispose === "function") {
+ obj.dispose();
+ }
+ if (obj[js_owned_gc_handle_symbol] === gc_handle) {
+ obj[js_owned_gc_handle_symbol] = GCHandleNull;
+ }
+ if (!_use_weak_ref && wr) wr.dispose();
+ doneGCHandles++;
+ } else {
+ keepSomeCsAlive = true;
}
- teardown_managed_proxy(obj, gchandle);
}
}
- // TODO: call C# to iterate and release all in JSHostImplementation.ThreadCsOwnedObjects
+ if (!keepSomeCsAlive) {
+ _js_owned_object_table.clear();
+ if (_use_finalization_registry) {
+ _js_owned_object_registry = new globalThis.FinalizationRegistry(_js_owned_object_finalized);
+ }
+ }
// dispose all proxies to JS objects
- for (const js_obj of _cs_owned_objects_by_js_handle) {
- if (js_obj) {
- const js_handle = js_obj[cs_owned_js_handle_symbol];
- if (js_handle) {
- mono_log_warn(`Proxy of JS object with JSHandleandle ${js_handle} was still alive`);
- mono_wasm_release_cs_owned_object(js_handle);
+ for (let js_handle = 0; js_handle < _cs_owned_objects_by_js_handle.length; js_handle++) {
+ const obj = _cs_owned_objects_by_js_handle[js_handle];
+ const keepAlive = obj && typeof obj[do_not_force_dispose] === "boolean" && obj[do_not_force_dispose];
+ if (!keepAlive) {
+ _cs_owned_objects_by_js_handle[js_handle] = undefined;
+ }
+ if (obj) {
+ if (verbose) {
+ const proxy_debug = BuildConfiguration === "Debug" ? obj[proxy_debug_symbol] : undefined;
+ if (BuildConfiguration === "Debug" && proxy_debug) {
+ mono_log_warn(`${proxy_debug} ${typeof obj} was still alive. ${keepAlive ? "keeping" : "disposing"}.`);
+ } else {
+ mono_log_warn(`Proxy of JS ${typeof obj} with JSHandle ${js_handle} was still alive. ${keepAlive ? "keeping" : "disposing"}.`);
+ }
+ }
+ if (!keepAlive) {
+ const promise_control = loaderHelpers.getPromiseController(obj);
+ if (promise_control) {
+ promise_control.reject(new Error("WebWorker which is origin of the Task is being terminated."));
+ }
+ if (typeof obj.dispose === "function") {
+ obj.dispose();
+ }
+ if (obj[cs_owned_js_handle_symbol] === js_handle) {
+ obj[cs_owned_js_handle_symbol] = undefined;
+ }
+ doneJSHandles++;
+ } else {
+ keepSomeJsAlive = true;
+ }
+ }
+ }
+ if (!keepSomeJsAlive) {
+ _cs_owned_objects_by_js_handle.length = 1;
+ _next_js_handle = 1;
+ _js_handle_free_list.length = 0;
+ }
+
+ if (disposeMethods) {
+ // dispose all [JSImport]
+ for (const bound_fn of fn_wrapper_by_fn_handle) {
+ if (bound_fn) {
+ const closure = (<any>bound_fn)[imported_js_function_symbol];
+ if (closure) {
+ closure.disposed = true;
+ doneImports++;
+ }
+ }
+ }
+ fn_wrapper_by_fn_handle.length = 1;
+
+ // dispose all [JSExport]
+ const assemblyExports = [...exportsByAssembly.values()];
+ for (const assemblyExport of assemblyExports) {
+ for (const exportName in assemblyExport) {
+ const bound_fn = assemblyExport[exportName];
+ const closure = bound_fn[bound_cs_function_symbol];
+ if (closure) {
+ closure.disposed = true;
+ doneExports++;
+ }
}
}
+ exportsByAssembly.clear();
}
+ mono_log_info(`forceDisposeProxies done: ${doneImports} imports, ${doneExports} exports, ${doneGCHandles} GCHandles, ${doneJSHandles} JSHandles.`);
}
\ No newline at end of file
args_count,
arg_marshalers,
res_converter,
+ isDisposed: false,
};
let bound_fn: Function;
if (args_count == 0 && !res_converter) {
}
}
- (<any>bound_fn)[bound_cs_function_symbol] = true;
+ (<any>bound_fn)[bound_cs_function_symbol] = closure;
_walk_exports_to_set_function(assembly, namespace, classname, methodname, signature_hash, bound_fn);
endMeasure(mark, MeasuredBlock.bindCsFunction, js_fqn);
function bind_fn_0V(closure: BindingClosure) {
const method = closure.method;
const fqn = closure.fqn;
- (<any>closure) = null;
+ if (!MonoWasmThreads) (<any>closure) = null;
return function bound_fn_0V() {
const mark = startMeasure();
loaderHelpers.assert_runtime_running();
+ mono_assert(!MonoWasmThreads || !closure.isDisposed, "The function was already disposed");
const sp = Module.stackSave();
try {
const args = alloc_stack_frame(2);
const method = closure.method;
const marshaler1 = closure.arg_marshalers[0]!;
const fqn = closure.fqn;
- (<any>closure) = null;
+ if (!MonoWasmThreads) (<any>closure) = null;
return function bound_fn_1V(arg1: any) {
const mark = startMeasure();
loaderHelpers.assert_runtime_running();
+ mono_assert(!MonoWasmThreads || !closure.isDisposed, "The function was already disposed");
const sp = Module.stackSave();
try {
const args = alloc_stack_frame(3);
const marshaler1 = closure.arg_marshalers[0]!;
const res_converter = closure.res_converter!;
const fqn = closure.fqn;
- (<any>closure) = null;
+ if (!MonoWasmThreads) (<any>closure) = null;
return function bound_fn_1R(arg1: any) {
const mark = startMeasure();
loaderHelpers.assert_runtime_running();
+ mono_assert(!MonoWasmThreads || !closure.isDisposed, "The function was already disposed");
const sp = Module.stackSave();
try {
const args = alloc_stack_frame(3);
const marshaler2 = closure.arg_marshalers[1]!;
const res_converter = closure.res_converter!;
const fqn = closure.fqn;
- (<any>closure) = null;
+ if (!MonoWasmThreads) (<any>closure) = null;
return function bound_fn_2R(arg1: any, arg2: any) {
const mark = startMeasure();
loaderHelpers.assert_runtime_running();
+ mono_assert(!MonoWasmThreads || !closure.isDisposed, "The function was already disposed");
const sp = Module.stackSave();
try {
const args = alloc_stack_frame(4);
const res_converter = closure.res_converter;
const method = closure.method;
const fqn = closure.fqn;
- (<any>closure) = null;
+ if (!MonoWasmThreads) (<any>closure) = null;
return function bound_fn(...js_args: any[]) {
const mark = startMeasure();
loaderHelpers.assert_runtime_running();
+ mono_assert(!MonoWasmThreads || !closure.isDisposed, "The function was already disposed");
const sp = Module.stackSave();
try {
const args = alloc_stack_frame(2 + args_count);
method: MonoMethod,
arg_marshalers: (BoundMarshalerToCs)[],
res_converter: BoundMarshalerToJs | undefined,
+ isDisposed: boolean,
}
export function invoke_method_and_handle_exception(method: MonoMethod, args: JSMarshalerArguments): void {
import { wrap_as_cancelable_promise } from "./cancelable-promise";
import { assert_synchronization_context } from "./pthreads/shared";
-const fn_wrapper_by_fn_handle: Function[] = <any>[null];// 0th slot is dummy, we never free bound functions
+export const fn_wrapper_by_fn_handle: Function[] = <any>[null];// 0th slot is dummy, main thread we free them on shutdown. On web worker thread we free them when worker is detached.
export function mono_wasm_bind_js_function(function_name: MonoStringRef, module_name: MonoStringRef, signature: JSFunctionSignature, function_js_handle: Int32Ptr, is_exception: Int32Ptr, result_address: MonoObjectRef): void {
assert_bindings();
arg_marshalers,
res_converter,
has_cleanup,
- arg_cleanup
+ arg_cleanup,
+ isDisposed: false,
};
let bound_fn: Function;
if (args_count == 0 && !res_converter) {
}
}
- (<any>bound_fn)[imported_js_function_symbol] = true;
+ (<any>bound_fn)[imported_js_function_symbol] = closure;
const fn_handle = fn_wrapper_by_fn_handle.length;
fn_wrapper_by_fn_handle.push(bound_fn);
setI32(function_js_handle, <any>fn_handle);
function bind_fn_0V(closure: BindingClosure) {
const fn = closure.fn;
const fqn = closure.fqn;
- (<any>closure) = null;
+ if (!MonoWasmThreads) (<any>closure) = null;
return function bound_fn_0V(args: JSMarshalerArguments) {
const mark = startMeasure();
try {
+ mono_assert(!MonoWasmThreads || !closure.isDisposed, "The function was already disposed");
// call user function
fn();
} catch (ex) {
const fn = closure.fn;
const marshaler1 = closure.arg_marshalers[0]!;
const fqn = closure.fqn;
- (<any>closure) = null;
+ if (!MonoWasmThreads) (<any>closure) = null;
return function bound_fn_1V(args: JSMarshalerArguments) {
const mark = startMeasure();
try {
+ mono_assert(!MonoWasmThreads || !closure.isDisposed, "The function was already disposed");
const arg1 = marshaler1(args);
// call user function
fn(arg1);
const marshaler1 = closure.arg_marshalers[0]!;
const res_converter = closure.res_converter!;
const fqn = closure.fqn;
- (<any>closure) = null;
+ if (!MonoWasmThreads) (<any>closure) = null;
return function bound_fn_1R(args: JSMarshalerArguments) {
const mark = startMeasure();
try {
+ mono_assert(!MonoWasmThreads || !closure.isDisposed, "The function was already disposed");
const arg1 = marshaler1(args);
// call user function
const js_result = fn(arg1);
const marshaler2 = closure.arg_marshalers[1]!;
const res_converter = closure.res_converter!;
const fqn = closure.fqn;
- (<any>closure) = null;
+ if (!MonoWasmThreads) (<any>closure) = null;
return function bound_fn_2R(args: JSMarshalerArguments) {
const mark = startMeasure();
try {
+ mono_assert(!MonoWasmThreads || !closure.isDisposed, "The function was already disposed");
const arg1 = marshaler1(args);
const arg2 = marshaler2(args);
// call user function
const has_cleanup = closure.has_cleanup;
const fn = closure.fn;
const fqn = closure.fqn;
- (<any>closure) = null;
+ if (!MonoWasmThreads) (<any>closure) = null;
return function bound_fn(args: JSMarshalerArguments) {
const mark = startMeasure();
try {
+ mono_assert(!MonoWasmThreads || !closure.isDisposed, "The function was already disposed");
const js_args = new Array(args_count);
for (let index = 0; index < args_count; index++) {
const marshaler = arg_marshalers[index]!;
type BindingClosure = {
fn: Function,
fqn: string,
+ isDisposed: boolean,
args_count: number,
arg_marshalers: (BoundMarshalerToJs)[],
res_converter: BoundMarshalerToCs | undefined,
// The .NET Foundation licenses this file to you under the MIT license.
import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_WEB, INTERNAL, loaderHelpers, mono_assert, runtimeHelpers } from "./globals";
-import { mono_log_debug, consoleWebSocket, mono_log_error, mono_log_info_no_prefix } from "./logging";
+import { mono_log_debug, consoleWebSocket, mono_log_error, mono_log_info_no_prefix, mono_log_warn } from "./logging";
export function is_exited() {
return loaderHelpers.exitCode !== undefined;
if (!is_exited()) {
try {
- reason.stack;
if (!runtimeHelpers.runtimeReady) {
mono_log_debug("abort_startup, reason: " + reason);
abort_promises(reason);
logErrorOnExit(exit_code, reason);
appendElementOnExit(exit_code);
if (runtimeHelpers.jiterpreter_dump_stats) runtimeHelpers.jiterpreter_dump_stats(false);
+ if (exit_code === 0 && loaderHelpers.config?.interopCleanupOnExit) {
+ runtimeHelpers.forceDisposeProxies(true, true);
+ }
}
- catch {
- // ignore any failures
+ catch (err) {
+ mono_log_warn("mono_exit failed", err);
+ // don't propagate any failures
}
- // TODO forceDisposeProxies(); here
-
loaderHelpers.exitCode = exit_code;
}
}
// internal
+ withInteropCleanupOnExit(): DotnetHostBuilder {
+ try {
+ deep_merge_config(monoConfig, {
+ interopCleanupOnExit: true
+ });
+ return this;
+ } catch (err) {
+ mono_exit(1, err);
+ throw err;
+ }
+ }
+
+ // internal
withAssertAfterExit(): DotnetHostBuilder {
try {
deep_merge_config(monoConfig, {
import { invoke_method_and_handle_exception } from "./invoke-cs";
import { marshal_array_to_cs, marshal_array_to_cs_impl, marshal_exception_to_cs, marshal_intptr_to_cs } from "./marshal-to-cs";
import { marshal_int32_to_js, marshal_string_to_js, marshal_task_to_js } from "./marshal-to-js";
+import { do_not_force_dispose } from "./gc-handles";
export function init_managed_exports(): void {
const exports_fqn_asm = "System.Runtime.InteropServices.JavaScript";
if (promise === null || promise === undefined) {
promise = Promise.resolve(0);
}
+ (promise as any)[do_not_force_dispose] = true; // prevent disposing the task in forceDisposeProxies()
return await promise;
} finally {
Module.runtimeKeepalivePop();// after await promise !
// The .NET Foundation licenses this file to you under the MIT license.
import MonoWasmThreads from "consts:monoWasmThreads";
+import BuildConfiguration from "consts:configuration";
+
import { isThenable } from "./cancelable-promise";
import cwraps from "./cwraps";
import { assert_not_disposed, cs_owned_js_handle_symbol, js_owned_gc_handle_symbol, mono_wasm_get_js_handle, setup_managed_proxy, teardown_managed_proxy } from "./gc-handles";
set_arg_length, get_arg, get_signature_arg1_type, get_signature_arg2_type, js_to_cs_marshalers,
get_signature_res_type, bound_js_function_symbol, set_arg_u16, array_element_size,
get_string_root, Span, ArraySegment, MemoryViewType, get_signature_arg3_type, set_arg_i64_big, set_arg_intptr, IDisposable,
- set_arg_element_type, ManagedObject, JavaScriptMarshalerArgSize
+ set_arg_element_type, ManagedObject, JavaScriptMarshalerArgSize, proxy_debug_symbol
} from "./marshal";
import { get_marshaler_to_js_by_type } from "./marshal-to-js";
import { _zero_region, localHeapViewF64, localHeapViewI32, localHeapViewU8 } from "./memory";
mono_check(value && value instanceof Function, "Value is not a Function");
// TODO: we could try to cache value -> existing JSHandle
- const marshal_function_to_cs_wrapper: any = (args: JSMarshalerArguments) => {
+ const wrapper: any = (args: JSMarshalerArguments) => {
const exc = get_arg(args, 0);
const res = get_arg(args, 1);
const arg1 = get_arg(args, 2);
const arg3 = get_arg(args, 4);
try {
+ mono_assert(!MonoWasmThreads || !wrapper.isDisposed, "Function is disposed and should not be invoked anymore.");
+
let arg1_js: any = undefined;
let arg2_js: any = undefined;
let arg3_js: any = undefined;
}
};
- marshal_function_to_cs_wrapper[bound_js_function_symbol] = true;
- const bound_function_handle = mono_wasm_get_js_handle(marshal_function_to_cs_wrapper)!;
+ wrapper[bound_js_function_symbol] = true;
+ wrapper.isDisposed = false;
+ wrapper.dispose = () => { wrapper.isDisposed = true; };
+ const bound_function_handle = mono_wasm_get_js_handle(wrapper)!;
+ if (BuildConfiguration === "Debug") {
+ wrapper[proxy_debug_symbol] = `Proxy of JS Function with JSHandle ${bound_function_handle}: ${value.toString()}`;
+ }
set_js_handle(arg, bound_function_handle);
set_arg_type(arg, MarshalerType.Function);//TODO or action ?
}
set_arg_type(arg, MarshalerType.Task);
const holder = new TaskCallbackHolder(value);
setup_managed_proxy(holder, gc_handle);
+ if (BuildConfiguration === "Debug") {
+ (holder as any)[proxy_debug_symbol] = `C# Task with GCHandle ${gc_handle}`;
+ }
if (MonoWasmThreads)
addUnsettledPromise();
value.then(data => {
try {
loaderHelpers.assert_runtime_running();
+ mono_assert(!holder.isDisposed, "This promise can't be propagated to managed code, because the Task was already freed.");
if (MonoWasmThreads)
settleUnsettledPromise();
runtimeHelpers.javaScriptExports.complete_task(gc_handle, null, data, res_converter || _marshal_cs_object_to_cs);
}).catch(reason => {
try {
loaderHelpers.assert_runtime_running();
+ mono_assert(!holder.isDisposed, "This promise can't be propagated to managed code, because the Task was already freed.");
if (MonoWasmThreads)
settleUnsettledPromise();
runtimeHelpers.javaScriptExports.complete_task(gc_handle, reason, null, undefined);
set_arg_type(arg, MarshalerType.JSException);
const message = value.toString();
_marshal_string_to_cs_impl(arg, message);
-
const known_js_handle = value[cs_owned_js_handle_symbol];
if (known_js_handle) {
set_js_handle(arg, known_js_handle);
}
else {
const js_handle = mono_wasm_get_js_handle(value)!;
+ if (BuildConfiguration === "Debug" && Object.isExtensible(value)) {
+ value[proxy_debug_symbol] = `JS Error with JSHandle ${js_handle}`;
+ }
set_js_handle(arg, js_handle);
}
}
set_arg_type(arg, MarshalerType.JSObject);
const js_handle = mono_wasm_get_js_handle(value)!;
+ if (BuildConfiguration === "Debug" && Object.isExtensible(value)) {
+ value[proxy_debug_symbol] = `JS Object with JSHandle ${js_handle}`;
+ }
set_js_handle(arg, js_handle);
}
}
else if (js_type == "object") {
const js_handle = mono_wasm_get_js_handle(value);
set_arg_type(arg, MarshalerType.JSObject);
+ if (BuildConfiguration === "Debug" && Object.isExtensible(value)) {
+ value[proxy_debug_symbol] = `JS Object with JSHandle ${js_handle}`;
+ }
set_js_handle(arg, js_handle);
}
else {
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+import MonoWasmThreads from "consts:monoWasmThreads";
+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 { Module, createPromiseController, loaderHelpers, mono_assert, runtimeHelpers } from "./globals";
get_arg_b8, get_arg_date, get_arg_length, set_js_handle, get_arg, set_arg_type,
get_signature_arg2_type, get_signature_arg1_type, cs_to_js_marshalers,
get_signature_res_type, get_arg_u16, array_element_size, get_string_root,
- ArraySegment, Span, MemoryViewType, get_signature_arg3_type, get_arg_i64_big, get_arg_intptr, get_arg_element_type, JavaScriptMarshalerArgSize
+ ArraySegment, Span, MemoryViewType, get_signature_arg3_type, get_arg_i64_big, get_arg_intptr, get_arg_element_type, JavaScriptMarshalerArgSize, proxy_debug_symbol
} from "./marshal";
import { monoStringToString } from "./strings";
import { JSHandleNull, GCHandleNull, JSMarshalerArgument, JSMarshalerArguments, JSMarshalerType, MarshalerToCs, MarshalerToJs, BoundMarshalerToJs, MarshalerType } from "./types/internal";
if (result === null || result === undefined) {
// this will create new Function for the C# delegate
result = (arg1_js: any, arg2_js: any, arg3_js: any): any => {
+ mono_assert(!MonoWasmThreads || !result.isDisposed, "Delegate is disposed and should not be invoked anymore.");
// arg numbers are shifted by one, the real first is a gc handle of the callback
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;
+ };
+ result.isDisposed = false;
+ if (BuildConfiguration === "Debug") {
+ (result as any)[proxy_debug_symbol] = `C# Delegate with GCHandle ${gc_handle}`;
+ }
setup_managed_proxy(result, gc_handle);
}
}
const promise = mono_wasm_get_jsobj_from_js_handle(js_handle);
mono_assert(!!promise, () => `ERR28: promise not found for js_handle: ${js_handle} `);
+ if (BuildConfiguration === "Debug") {
+ (promise as any)[proxy_debug_symbol] = `JS Promise with JSHandle ${js_handle}`;
+ }
loaderHelpers.assertIsControllablePromise<any>(promise);
const promise_control = loaderHelpers.getPromiseController(promise);
if (js_handle === JSHandleNull) {
const { promise, promise_control } = createPromiseController();
const js_handle = mono_wasm_get_js_handle(promise)!;
+ if (BuildConfiguration === "Debug" && Object.isExtensible(promise)) {
+ (promise as any)[proxy_debug_symbol] = `JS Promise with JSHandle ${js_handle}`;
+ }
set_js_handle(res, js_handle);
if (exc_type !== MarshalerType.None) {
const message = marshal_string_to_js(arg);
result = new ManagedError(message!);
+ if (BuildConfiguration === "Debug") {
+ (result as any)[proxy_debug_symbol] = `C# Exception with GCHandle ${gc_handle}`;
+ }
setup_managed_proxy(result, gc_handle);
}
// If the JS object for this gc_handle was already collected (or was never created)
if (!result) {
result = new ManagedObject();
+ if (BuildConfiguration === "Debug") {
+ (result as any)[proxy_debug_symbol] = `C# Object with GCHandle ${gc_handle}`;
+ }
setup_managed_proxy(result, gc_handle);
}
throw new Error(`NotImplementedException ${MarshalerType[element_type]} `);
}
const gc_handle = get_arg_gc_handle(arg);
+ if (BuildConfiguration === "Debug") {
+ (result as any)[proxy_debug_symbol] = `C# ArraySegment with GCHandle ${gc_handle}`;
+ }
setup_managed_proxy(result, gc_handle);
return result;
export const bound_cs_function_symbol = Symbol.for("wasm bound_cs_function");
export const bound_js_function_symbol = Symbol.for("wasm bound_js_function");
export const imported_js_function_symbol = Symbol.for("wasm imported_js_function");
+export const proxy_debug_symbol = Symbol.for("wasm proxy_debug");
/**
* JSFunctionSignature is pointer to [
mono_assert(runtimeHelpers.mono_wasm_bindings_is_ready, "JS interop is not installed on this worker.");
mono_assert(!uninstall_js_synchronization_context || runtimeHelpers.jsSynchronizationContextInstalled, "JSSynchronizationContext is not installed on this worker.");
- forceDisposeProxies(false);
if (uninstall_js_synchronization_context) {
+ forceDisposeProxies(true, runtimeHelpers.diagnosticTracing);
Module.runtimeKeepalivePop();
}
// Now we remove assets collection from the hash.
delete inputs.assets;
+ delete inputs.resources;
// some things are calculated at runtime, so we need to add them to the hash
inputs.preferredIcuAsset = loaderHelpers.preferredIcuAsset;
// timezone is part of env variables, so it is already in the hash
delete inputs.diagnosticTracing;
delete inputs.appendElementOnExit;
delete inputs.assertAfterExit;
+ delete inputs.interopCleanupOnExit;
delete inputs.logExitCode;
delete inputs.pthreadPoolSize;
delete inputs.asyncFlushOnExit;
delete inputs.maxParallelDownloads;
delete inputs.enableDownloadRetry;
delete inputs.exitAfterSnapshot;
+ delete inputs.extensions;
inputs.GitHash = GitHash;
inputs.ProductVersion = ProductVersion;
import { cwraps_binding_api, cwraps_mono_api } from "./net6-legacy/exports-legacy";
import { BINDING, MONO } from "./net6-legacy/globals";
import { localHeapViewU8 } from "./memory";
+import { assertNoProxies } from "./gc-handles";
// default size if MonoConfig.pthreadPoolSize is undefined
const MONO_PTHREAD_POOL_SIZE = 4;
}
export function postRunWorker() {
+ assertNoProxies();
// signal next stage
runtimeHelpers.runtimeReady = false;
runtimeHelpers.afterPreRun = createPromiseController<void>();
run(): Promise<number>
}
+// when adding new fields, please consider if it should be impacting the snapshot hash. If not, please drop it in the snapshot getCacheKey()
export type MonoConfig = {
/**
* The subfolder containing managed assemblies and pdbs. This is relative to dotnet.js script.
return ptr as T;
}
+// when adding new fields, please consider if it should be impacting the snapshot hash. If not, please drop it in the snapshot getCacheKey()
export type MonoConfigInternal = MonoConfig & {
runtimeOptions?: string[], // array of runtime options as strings
aotProfilerOptions?: AOTProfilerOptions, // dictionary-style Object. If omitted, aot profiler will not be initialized.
waitForDebugger?: number,
appendElementOnExit?: boolean
assertAfterExit?: boolean // default true for shell/nodeJS
+ interopCleanupOnExit?: boolean
logExitCode?: boolean
forwardConsoleLogsToWS?: boolean,
asyncFlushOnExit?: boolean
instantiate_asset: (asset: AssetEntry, url: string, bytes: Uint8Array) => void,
instantiate_symbols_asset: (pendingAsset: AssetEntryInternal) => Promise<void>,
jiterpreter_dump_stats?: (x: boolean) => string,
+ forceDisposeProxies: (disposeMethods: boolean, verbose: boolean) => void,
}
export type AOTProfilerOptions = {
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-const _use_weak_ref = typeof globalThis.WeakRef === "function";
+export const _use_weak_ref = typeof globalThis.WeakRef === "function";
export function create_weak_ref<T extends object>(js_obj: T): WeakRef<T> {
if (_use_weak_ref) {
return <any>{
deref: () => {
return js_obj;
+ },
+ dispose: () => {
+ js_obj = null!;
}
};
}
ws[wasm_ws_is_aborted] = true;
const open_promise_control = ws[wasm_ws_pending_open_promise];
if (open_promise_control) {
- open_promise_control.reject("OperationCanceledException");
+ open_promise_control.reject(new Error("OperationCanceledException"));
}
for (const close_promise_control of ws[wasm_ws_pending_close_promises]) {
- close_promise_control.reject("OperationCanceledException");
+ close_promise_control.reject(new Error("OperationCanceledException"));
}
for (const send_promise_control of ws[wasm_ws_pending_send_promises]) {
- send_promise_control.reject("OperationCanceledException");
+ send_promise_control.reject(new Error("OperationCanceledException"));
}
ws[wasm_ws_pending_receive_promise_queue].drain(receive_promise_control => {
- receive_promise_control.reject("OperationCanceledException");
+ receive_promise_control.reject(new Error("OperationCanceledException"));
});
// this is different from Managed implementation
.withExitOnUnhandledError()
.withExitCodeLogging()
.withElementOnExit()
+ .withInteropCleanupOnExit()
.withAssertAfterExit()
.withConfig({
loadAllSatelliteResources: true