Skip to content

Commit 8e23fec

Browse files
authored
[browser][MT] Fix Promise cancelation (#99397)
1 parent 80c0df6 commit 8e23fec

File tree

21 files changed

+73
-48
lines changed

21 files changed

+73
-48
lines changed

src/mono/browser/runtime/cancelable-promise.ts

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ export function wrap_as_cancelable<T>(inner: Promise<T>): ControllablePromise<T>
3636
}
3737

3838
export function mono_wasm_cancel_promise(task_holder_gc_handle: GCHandle): void {
39+
if (!loaderHelpers.is_runtime_running()) {
40+
mono_log_debug("This promise can't be canceled, mono runtime already exited.");
41+
return;
42+
}
3943
const holder = _lookup_js_owned_object(task_holder_gc_handle) as PromiseHolder;
4044
mono_assert(!!holder, () => `Expected Promise for GCHandle ${task_holder_gc_handle}`);
4145
holder.cancel();
@@ -75,6 +79,11 @@ export class PromiseHolder extends ManagedObject {
7579

7680
resolve(data: any) {
7781
mono_assert(!this.isResolved, "resolve could be called only once");
82+
mono_assert(!this.isDisposed, "resolve is already disposed.");
83+
if (!loaderHelpers.is_runtime_running()) {
84+
mono_log_debug("This promise resolution can't be propagated to managed code, mono runtime already exited.");
85+
return;
86+
}
7887
if (WasmEnableThreads && !this.setIsResolving()) {
7988
// we know that cancelation is in flight
8089
// because we need to keep the GCHandle alive until until the cancelation arrives
@@ -89,11 +98,16 @@ export class PromiseHolder extends ManagedObject {
8998
return;
9099
}
91100
this.isResolved = true;
92-
this.complete_task(data, null);
101+
this.complete_task_wrapper(data, null);
93102
}
94103

95104
reject(reason: any) {
96105
mono_assert(!this.isResolved, "reject could be called only once");
106+
mono_assert(!this.isDisposed, "resolve is already disposed.");
107+
if (!loaderHelpers.is_runtime_running()) {
108+
mono_log_debug("This promise rejection can't be propagated to managed code, mono runtime already exited.");
109+
return;
110+
}
97111
const isCancelation = reason && reason[promise_holder_symbol] === this;
98112
if (WasmEnableThreads && !isCancelation && !this.setIsResolving()) {
99113
// we know that cancelation is in flight
@@ -109,21 +123,22 @@ export class PromiseHolder extends ManagedObject {
109123
return;
110124
}
111125
this.isResolved = true;
112-
this.complete_task(null, reason);
126+
this.complete_task_wrapper(null, reason);
113127
}
114128

115129
cancel() {
116130
mono_assert(!this.isResolved, "cancel could be called only once");
131+
mono_assert(!this.isDisposed, "resolve is already disposed.");
117132

118133
if (this.isPostponed) {
119134
// there was racing resolve/reject which was postponed, to retain valid GCHandle
120135
// in this case we just finish the original resolve/reject
121136
// and we need to use the postponed data/reason
122137
this.isResolved = true;
123138
if (this.reason !== undefined) {
124-
this.complete_task(null, this.reason);
139+
this.complete_task_wrapper(null, this.reason);
125140
} else {
126-
this.complete_task(this.data, null);
141+
this.complete_task_wrapper(this.data, null);
127142
}
128143
} else {
129144
// there is no racing resolve/reject, we can reject/cancel the promise
@@ -138,11 +153,7 @@ export class PromiseHolder extends ManagedObject {
138153
}
139154

140155
// we can do this just once, because it will be dispose the GCHandle
141-
complete_task(data: any, reason: any) {
142-
if (!loaderHelpers.is_runtime_running()) {
143-
mono_log_debug("This promise can't be propagated to managed code, mono runtime already exited.");
144-
return;
145-
}
156+
complete_task_wrapper(data: any, reason: any) {
146157
try {
147158
mono_assert(!this.isPosted, "Promise is already posted to managed.");
148159
this.isPosted = true;

src/mono/browser/runtime/dotnet.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,13 @@ type APIType = {
446446
* @returns exit code of the Main() method.
447447
*/
448448
runMainAndExit: (mainAssemblyName?: string, args?: string[]) => Promise<number>;
449+
/**
450+
* Exits the runtime.
451+
* Note: after the runtime exits, it would reject all further calls to the API.
452+
* @param code "process" exit code.
453+
* @param reason could be a string or an Error object.
454+
*/
455+
exit: (code: number, reason?: any) => void;
449456
/**
450457
* Sets the environment variable for the "process"
451458
* @param name

src/mono/browser/runtime/export-api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export function export_api(): any {
1414
const api: APIType = {
1515
runMain: mono_run_main,
1616
runMainAndExit: mono_run_main_and_exit,
17+
exit: loaderHelpers.mono_exit,
1718
setEnvironmentVariable: mono_wasm_setenv,
1819
getAssemblyExports: mono_wasm_get_assembly_exports,
1920
setModuleImports: mono_wasm_set_module_imports,

src/mono/browser/runtime/gc-handles.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,9 @@ export function setup_managed_proxy(owner: any, gc_handle: GCHandle): void {
139139

140140
export function upgrade_managed_proxy_to_strong_ref(owner: any, gc_handle: GCHandle): void {
141141
const sr = create_strong_ref(owner);
142+
if (_use_finalization_registry) {
143+
_js_owned_object_registry.unregister(owner);
144+
}
142145
_js_owned_object_table.set(gc_handle, sr);
143146
}
144147

src/mono/browser/runtime/interp-pgo.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,6 @@ export async function getCacheKey(prefix: string): Promise<string | null> {
193193
delete inputs.forwardConsoleLogsToWS;
194194
delete inputs.diagnosticTracing;
195195
delete inputs.appendElementOnExit;
196-
delete inputs.assertAfterExit;
197196
delete inputs.interopCleanupOnExit;
198197
delete inputs.dumpThreadsOnNonZeroExit;
199198
delete inputs.logExitCode;

src/mono/browser/runtime/invoke-js.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export function mono_wasm_invoke_jsimport(signature: JSFunctionSignature, args:
5353

5454
export function mono_wasm_invoke_jsimport_ST(function_handle: JSFnHandle, args: JSMarshalerArguments): void {
5555
if (WasmEnableThreads) return;
56+
loaderHelpers.assert_runtime_running();
5657
const bound_fn = js_import_wrapper_by_fn_handle[<any>function_handle];
5758
mono_assert(bound_fn, () => `Imported function handle expected ${function_handle}`);
5859
bound_fn(args);
@@ -336,6 +337,7 @@ type BindingClosure = {
336337
}
337338

338339
export function mono_wasm_invoke_js_function(bound_function_js_handle: JSHandle, args: JSMarshalerArguments): void {
340+
loaderHelpers.assert_runtime_running();
339341
const bound_fn = mono_wasm_get_jsobj_from_js_handle(bound_function_js_handle);
340342
mono_assert(bound_fn && typeof (bound_fn) === "function" && bound_fn[bound_js_function_symbol], () => `Bound function handle expected ${bound_function_js_handle}`);
341343
bound_fn(args);

src/mono/browser/runtime/loader/config.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import WasmEnableThreads from "consts:wasmEnableThreads";
66

77
import { MainThreadingMode, type DotnetModuleInternal, type MonoConfigInternal, JSThreadBlockingMode, JSThreadInteropMode } from "../types/internal";
88
import type { DotnetModuleConfig, MonoConfig, ResourceGroups, ResourceList } from "../types";
9-
import { ENVIRONMENT_IS_WEB, exportedRuntimeAPI, loaderHelpers, runtimeHelpers } from "./globals";
9+
import { exportedRuntimeAPI, loaderHelpers, runtimeHelpers } from "./globals";
1010
import { mono_log_error, mono_log_debug } from "./logging";
1111
import { importLibraryInitializers, invokeLibraryInitializers } from "./libraryInitializers";
1212
import { mono_exit } from "./exit";
@@ -178,8 +178,6 @@ export function normalizeConfig() {
178178
}
179179
}
180180

181-
loaderHelpers.assertAfterExit = config.assertAfterExit = config.assertAfterExit || !ENVIRONMENT_IS_WEB;
182-
183181
if (config.debugLevel === undefined && BuildConfiguration === "Debug") {
184182
config.debugLevel = -1;
185183
}

src/mono/browser/runtime/loader/exit.ts

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,11 @@ export function is_runtime_running() {
1515
}
1616

1717
export function assert_runtime_running() {
18-
if (!is_exited()) {
19-
if (WasmEnableThreads && ENVIRONMENT_IS_WORKER) {
20-
mono_assert(runtimeHelpers.runtimeReady, "The WebWorker is not attached to the runtime. See https://github.com/dotnet/runtime/blob/main/src/mono/wasm/threads.md#JS-interop-on-dedicated-threads");
21-
} else {
22-
mono_assert(runtimeHelpers.runtimeReady, ".NET runtime didn't start yet. Please call dotnet.create() first.");
23-
}
18+
mono_assert(!is_exited(), () => `.NET runtime already exited with ${loaderHelpers.exitCode} ${loaderHelpers.exitReason}. You can use runtime.runMain() which doesn't exit the runtime.`);
19+
if (WasmEnableThreads && ENVIRONMENT_IS_WORKER) {
20+
mono_assert(runtimeHelpers.runtimeReady, "The WebWorker is not attached to the runtime. See https://github.com/dotnet/runtime/blob/main/src/mono/wasm/threads.md#JS-interop-on-dedicated-threads");
2421
} else {
25-
const message = `.NET runtime already exited with ${loaderHelpers.exitCode} ${loaderHelpers.exitReason}. You can use runtime.runMain() which doesn't exit the runtime.`;
26-
if (loaderHelpers.assertAfterExit) {
27-
mono_assert(false, message);
28-
} else {
29-
mono_log_warn(message);
30-
}
22+
mono_assert(runtimeHelpers.runtimeReady, ".NET runtime didn't start yet. Please call dotnet.create() first.");
3123
}
3224
}
3325

src/mono/browser/runtime/loader/globals.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,6 @@ export function setLoaderGlobals(
8686

8787
maxParallelDownloads: 16,
8888
enableDownloadRetry: true,
89-
assertAfterExit: !ENVIRONMENT_IS_WEB,
9089

9190
_loaded_files: [],
9291
loadedFiles: [],

src/mono/browser/runtime/loader/run.ts

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -138,19 +138,6 @@ export class HostBuilder implements DotnetHostBuilder {
138138
}
139139
}
140140

141-
// internal
142-
withAssertAfterExit(): DotnetHostBuilder {
143-
try {
144-
deep_merge_config(monoConfig, {
145-
assertAfterExit: true
146-
});
147-
return this;
148-
} catch (err) {
149-
mono_exit(1, err);
150-
throw err;
151-
}
152-
}
153-
154141
// internal
155142
// todo fallback later by debugLevel
156143
withWaitingForDebugger(level: number): DotnetHostBuilder {

0 commit comments

Comments
 (0)