diff options
author | Ben Noordhuis <info@bnoordhuis.nl> | 2021-11-28 00:46:12 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-11-28 00:46:12 +0100 |
commit | 2d830c263b0a3519c7fb75199a6ad1b8f10d2b51 (patch) | |
tree | 71a045c5b66b13ddaf1f4c43fc6031e0dfa41a8a /core/runtime.rs | |
parent | 993a1dd41ae5f96bdb24b09757e24c2ac24126d0 (diff) |
feat(core): intercept unhandled promise rejections (#12910)
Provide a programmatic means of intercepting rejected promises without a
.catch() handler. Needed for Node compat mode.
Also do a first pass at uncaughtException support because they're
closely intertwined in Node. It's like that Frank Sinatra song:
you can't have one without the other.
Stepping stone for #7013.
Diffstat (limited to 'core/runtime.rs')
-rw-r--r-- | core/runtime.rs | 94 |
1 files changed, 94 insertions, 0 deletions
diff --git a/core/runtime.rs b/core/runtime.rs index ad7f16886..6a3d694e6 100644 --- a/core/runtime.rs +++ b/core/runtime.rs @@ -144,6 +144,8 @@ pub(crate) struct JsRuntimeState { pub(crate) js_sync_cb: Option<v8::Global<v8::Function>>, pub(crate) js_macrotask_cbs: Vec<v8::Global<v8::Function>>, pub(crate) js_nexttick_cbs: Vec<v8::Global<v8::Function>>, + pub(crate) js_promise_reject_cb: Option<v8::Global<v8::Function>>, + pub(crate) js_uncaught_exception_cb: Option<v8::Global<v8::Function>>, pub(crate) has_tick_scheduled: bool, pub(crate) js_wasm_streaming_cb: Option<v8::Global<v8::Function>>, pub(crate) pending_promise_exceptions: @@ -351,6 +353,8 @@ impl JsRuntime { js_sync_cb: None, js_macrotask_cbs: vec![], js_nexttick_cbs: vec![], + js_promise_reject_cb: None, + js_uncaught_exception_cb: None, has_tick_scheduled: false, js_wasm_streaming_cb: None, js_error_create_fn, @@ -2642,4 +2646,94 @@ assertEquals(1, notify_return_value); .to_string() .contains("JavaScript execution has been terminated")); } + + #[tokio::test] + async fn test_set_promise_reject_callback() { + let promise_reject = Arc::new(AtomicUsize::default()); + let promise_reject_ = Arc::clone(&promise_reject); + + let uncaught_exception = Arc::new(AtomicUsize::default()); + let uncaught_exception_ = Arc::clone(&uncaught_exception); + + let op_promise_reject = move |_: &mut OpState, _: (), _: ()| { + promise_reject_.fetch_add(1, Ordering::Relaxed); + Ok(()) + }; + + let op_uncaught_exception = move |_: &mut OpState, _: (), _: ()| { + uncaught_exception_.fetch_add(1, Ordering::Relaxed); + Ok(()) + }; + + let extension = Extension::builder() + .ops(vec![("op_promise_reject", op_sync(op_promise_reject))]) + .ops(vec![( + "op_uncaught_exception", + op_sync(op_uncaught_exception), + )]) + .build(); + + let mut runtime = JsRuntime::new(RuntimeOptions { + extensions: vec![extension], + ..Default::default() + }); + + runtime + .execute_script( + "promise_reject_callback.js", + r#" + // Note: |promise| is not the promise created below, it's a child. + Deno.core.setPromiseRejectCallback((type, promise, reason) => { + if (type !== /* PromiseRejectWithNoHandler */ 0) { + throw Error("unexpected type: " + type); + } + if (reason.message !== "reject") { + throw Error("unexpected reason: " + reason); + } + Deno.core.opSync("op_promise_reject"); + throw Error("promiseReject"); // Triggers uncaughtException handler. + }); + + Deno.core.setUncaughtExceptionCallback((err) => { + if (err.message !== "promiseReject") throw err; + Deno.core.opSync("op_uncaught_exception"); + }); + + new Promise((_, reject) => reject(Error("reject"))); + "#, + ) + .unwrap(); + runtime.run_event_loop(false).await.unwrap(); + + assert_eq!(1, promise_reject.load(Ordering::Relaxed)); + assert_eq!(1, uncaught_exception.load(Ordering::Relaxed)); + + runtime + .execute_script( + "promise_reject_callback.js", + r#" + { + const prev = Deno.core.setPromiseRejectCallback((...args) => { + prev(...args); + }); + } + + { + const prev = Deno.core.setUncaughtExceptionCallback((...args) => { + prev(...args); + throw Error("fail"); + }); + } + + new Promise((_, reject) => reject(Error("reject"))); + "#, + ) + .unwrap(); + // Exception from uncaughtException handler doesn't bubble up but is + // printed to stderr. + runtime.run_event_loop(false).await.unwrap(); + + assert_eq!(2, promise_reject.load(Ordering::Relaxed)); + assert_eq!(2, uncaught_exception.load(Ordering::Relaxed)); + } } |