summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSatya Rohith <me@satyarohith.com>2024-04-16 19:15:41 +0530
committerGitHub <noreply@github.com>2024-04-16 13:45:41 +0000
commit50223c5c532332f4a296b12b027f85429f529690 (patch)
tree75d4494d1ade596038ad52ac0241addcc3a124c2
parent0a7f46b8c29d67b579e4ffd4681aa5d0b7e30c6b (diff)
fix(ext/node): dispatch beforeExit/exit events irrespective of listeners (#23382)
Closes https://github.com/denoland/deno/issues/23342 Closes https://github.com/denoland/deno/issues/21757
-rw-r--r--cli/tools/bench/mod.rs2
-rw-r--r--cli/tools/coverage/mod.rs1
-rw-r--r--cli/worker.rs21
-rw-r--r--ext/node/polyfills/process.ts20
-rw-r--r--runtime/js/99_main.js6
-rw-r--r--runtime/worker.rs73
-rw-r--r--tests/integration/node_compat_tests.rs14
-rw-r--r--tests/testdata/node/events_order.out12
-rw-r--r--tests/testdata/node/events_order.ts25
-rw-r--r--tests/testdata/node/process_beforeexit_exit_events.out2
-rw-r--r--tests/testdata/node/process_beforeexit_exit_events.ts9
11 files changed, 164 insertions, 21 deletions
diff --git a/cli/tools/bench/mod.rs b/cli/tools/bench/mod.rs
index 88bff479c..95986f320 100644
--- a/cli/tools/bench/mod.rs
+++ b/cli/tools/bench/mod.rs
@@ -269,7 +269,9 @@ async fn bench_specifier_inner(
// Ignore `defaultPrevented` of the `beforeunload` event. We don't allow the
// event loop to continue beyond what's needed to await results.
worker.dispatch_beforeunload_event()?;
+ worker.dispatch_process_beforeexit_event()?;
worker.dispatch_unload_event()?;
+ worker.dispatch_process_exit_event()?;
// Ensure the worker has settled so we can catch any remaining unhandled rejections. We don't
// want to wait forever here.
diff --git a/cli/tools/coverage/mod.rs b/cli/tools/coverage/mod.rs
index 67fe979b5..763022c1f 100644
--- a/cli/tools/coverage/mod.rs
+++ b/cli/tools/coverage/mod.rs
@@ -71,6 +71,7 @@ impl crate::worker::CoverageCollector for CoverageCollector {
// Filter out internal JS files from being included in coverage reports
if script_coverage.url.starts_with("ext:")
|| script_coverage.url.starts_with("[ext:")
+ || script_coverage.url.starts_with("node:")
{
continue;
}
diff --git a/cli/worker.rs b/cli/worker.rs
index 74ae1ef8f..070671e60 100644
--- a/cli/worker.rs
+++ b/cli/worker.rs
@@ -212,12 +212,17 @@ impl CliMainWorker {
.await?;
}
- if !self.worker.dispatch_beforeunload_event()? {
- break;
+ let web_continue = self.worker.dispatch_beforeunload_event()?;
+ if !web_continue {
+ let node_continue = self.worker.dispatch_process_beforeexit_event()?;
+ if !node_continue {
+ break;
+ }
}
}
self.worker.dispatch_unload_event()?;
+ self.worker.dispatch_process_exit_event()?;
if let Some(coverage_collector) = maybe_coverage_collector.as_mut() {
self
@@ -272,10 +277,13 @@ impl CliMainWorker {
Ok(()) => {}
Err(error) => break Err(error),
}
- match self.inner.worker.dispatch_beforeunload_event() {
- Ok(default_prevented) if default_prevented => {} // continue loop
- Ok(_) => break Ok(()),
- Err(error) => break Err(error),
+ let web_continue = self.inner.worker.dispatch_beforeunload_event()?;
+ if !web_continue {
+ let node_continue =
+ self.inner.worker.dispatch_process_beforeexit_event()?;
+ if !node_continue {
+ break Ok(());
+ }
}
};
self.pending_unload = false;
@@ -283,6 +291,7 @@ impl CliMainWorker {
result?;
self.inner.worker.dispatch_unload_event()?;
+ self.inner.worker.dispatch_process_exit_event()?;
Ok(())
}
diff --git a/ext/node/polyfills/process.ts b/ext/node/polyfills/process.ts
index 70c516c96..b89b7af52 100644
--- a/ext/node/polyfills/process.ts
+++ b/ext/node/polyfills/process.ts
@@ -802,15 +802,13 @@ function processOnError(event: ErrorEvent) {
uncaughtExceptionHandler(event.error, "uncaughtException");
}
-function processOnBeforeUnload(event: Event) {
+function dispatchProcessBeforeExitEvent() {
process.emit("beforeExit", process.exitCode || 0);
processTicksAndRejections();
- if (core.eventLoopHasMoreWork()) {
- event.preventDefault();
- }
+ return core.eventLoopHasMoreWork();
}
-function processOnUnload() {
+function dispatchProcessExitEvent() {
if (!process._exiting) {
process._exiting = true;
process.emit("exit", process.exitCode || 0);
@@ -856,16 +854,6 @@ function synchronizeListeners() {
} else {
globalThis.removeEventListener("error", processOnError);
}
- if (beforeExitListenerCount > 0) {
- globalThis.addEventListener("beforeunload", processOnBeforeUnload);
- } else {
- globalThis.removeEventListener("beforeunload", processOnBeforeUnload);
- }
- if (exitListenerCount > 0) {
- globalThis.addEventListener("unload", processOnUnload);
- } else {
- globalThis.removeEventListener("unload", processOnUnload);
- }
}
// Overwrites the 1st and 2nd items with getters.
@@ -880,6 +868,8 @@ Object.defineProperty(argv, "1", {
},
});
+internals.dispatchProcessBeforeExitEvent = dispatchProcessBeforeExitEvent;
+internals.dispatchProcessExitEvent = dispatchProcessExitEvent;
// Should be called only once, in `runtime/js/99_main.js` when the runtime is
// bootstrapped.
internals.__bootstrapNodeProcess = function (
diff --git a/runtime/js/99_main.js b/runtime/js/99_main.js
index 0241a1936..4c0a9b5e7 100644
--- a/runtime/js/99_main.js
+++ b/runtime/js/99_main.js
@@ -1005,6 +1005,10 @@ function bootstrapWorkerRuntime(
const nodeBootstrap = globalThis.nodeBootstrap;
delete globalThis.nodeBootstrap;
+const dispatchProcessExitEvent = internals.dispatchProcessExitEvent;
+delete internals.dispatchProcessExitEvent;
+const dispatchProcessBeforeExitEvent = internals.dispatchProcessBeforeExitEvent;
+delete internals.dispatchProcessBeforeExitEvent;
globalThis.bootstrap = {
mainRuntime: bootstrapMainRuntime,
@@ -1012,6 +1016,8 @@ globalThis.bootstrap = {
dispatchLoadEvent,
dispatchUnloadEvent,
dispatchBeforeUnloadEvent,
+ dispatchProcessExitEvent,
+ dispatchProcessBeforeExitEvent,
};
event.setEventTargetData(globalThis);
diff --git a/runtime/worker.rs b/runtime/worker.rs
index 236777335..956fa2d32 100644
--- a/runtime/worker.rs
+++ b/runtime/worker.rs
@@ -117,6 +117,8 @@ pub struct MainWorker {
dispatch_load_event_fn_global: v8::Global<v8::Function>,
dispatch_beforeunload_event_fn_global: v8::Global<v8::Function>,
dispatch_unload_event_fn_global: v8::Global<v8::Function>,
+ dispatch_process_beforeexit_event_fn_global: v8::Global<v8::Function>,
+ dispatch_process_exit_event_fn_global: v8::Global<v8::Function>,
}
pub struct WorkerOptions {
@@ -530,6 +532,8 @@ impl MainWorker {
dispatch_load_event_fn_global,
dispatch_beforeunload_event_fn_global,
dispatch_unload_event_fn_global,
+ dispatch_process_beforeexit_event_fn_global,
+ dispatch_process_exit_event_fn_global,
) = {
let context = js_runtime.main_context();
let scope = &mut js_runtime.handle_scope();
@@ -576,11 +580,39 @@ impl MainWorker {
.unwrap();
let dispatch_unload_event_fn =
v8::Local::<v8::Function>::try_from(dispatch_unload_event_fn).unwrap();
+ let dispatch_process_beforeexit_event =
+ v8::String::new_external_onebyte_static(
+ scope,
+ b"dispatchProcessBeforeExitEvent",
+ )
+ .unwrap();
+ let dispatch_process_beforeexit_event_fn = bootstrap_ns
+ .get(scope, dispatch_process_beforeexit_event.into())
+ .unwrap();
+ let dispatch_process_beforeexit_event_fn =
+ v8::Local::<v8::Function>::try_from(
+ dispatch_process_beforeexit_event_fn,
+ )
+ .unwrap();
+ let dispatch_process_exit_event =
+ v8::String::new_external_onebyte_static(
+ scope,
+ b"dispatchProcessExitEvent",
+ )
+ .unwrap();
+ let dispatch_process_exit_event_fn = bootstrap_ns
+ .get(scope, dispatch_process_exit_event.into())
+ .unwrap();
+ let dispatch_process_exit_event_fn =
+ v8::Local::<v8::Function>::try_from(dispatch_process_exit_event_fn)
+ .unwrap();
(
v8::Global::new(scope, bootstrap_fn),
v8::Global::new(scope, dispatch_load_event_fn),
v8::Global::new(scope, dispatch_beforeunload_event_fn),
v8::Global::new(scope, dispatch_unload_event_fn),
+ v8::Global::new(scope, dispatch_process_beforeexit_event_fn),
+ v8::Global::new(scope, dispatch_process_exit_event_fn),
)
};
@@ -594,6 +626,8 @@ impl MainWorker {
dispatch_load_event_fn_global,
dispatch_beforeunload_event_fn_global,
dispatch_unload_event_fn_global,
+ dispatch_process_beforeexit_event_fn_global,
+ dispatch_process_exit_event_fn_global,
}
}
@@ -782,6 +816,21 @@ impl MainWorker {
Ok(())
}
+ /// Dispatches process.emit("exit") event for node compat.
+ pub fn dispatch_process_exit_event(&mut self) -> Result<(), AnyError> {
+ let scope = &mut self.js_runtime.handle_scope();
+ let tc_scope = &mut v8::TryCatch::new(scope);
+ let dispatch_process_exit_event_fn =
+ v8::Local::new(tc_scope, &self.dispatch_process_exit_event_fn_global);
+ let undefined = v8::undefined(tc_scope);
+ dispatch_process_exit_event_fn.call(tc_scope, undefined.into(), &[]);
+ if let Some(exception) = tc_scope.exception() {
+ let error = JsError::from_v8_exception(tc_scope, exception);
+ return Err(error.into());
+ }
+ Ok(())
+ }
+
/// Dispatches "beforeunload" event to the JavaScript runtime. Returns a boolean
/// indicating if the event was prevented and thus event loop should continue
/// running.
@@ -800,4 +849,28 @@ impl MainWorker {
let ret_val = ret_val.unwrap();
Ok(ret_val.is_false())
}
+
+ /// Dispatches process.emit("beforeExit") event for node compat.
+ pub fn dispatch_process_beforeexit_event(
+ &mut self,
+ ) -> Result<bool, AnyError> {
+ let scope = &mut self.js_runtime.handle_scope();
+ let tc_scope = &mut v8::TryCatch::new(scope);
+ let dispatch_process_beforeexit_event_fn = v8::Local::new(
+ tc_scope,
+ &self.dispatch_process_beforeexit_event_fn_global,
+ );
+ let undefined = v8::undefined(tc_scope);
+ let ret_val = dispatch_process_beforeexit_event_fn.call(
+ tc_scope,
+ undefined.into(),
+ &[],
+ );
+ if let Some(exception) = tc_scope.exception() {
+ let error = JsError::from_v8_exception(tc_scope, exception);
+ return Err(error.into());
+ }
+ let ret_val = ret_val.unwrap();
+ Ok(ret_val.is_true())
+ }
}
diff --git a/tests/integration/node_compat_tests.rs b/tests/integration/node_compat_tests.rs
index c4c4ba1fa..9dfd07ab4 100644
--- a/tests/integration/node_compat_tests.rs
+++ b/tests/integration/node_compat_tests.rs
@@ -21,3 +21,17 @@ itest!(node_test_module_no_sanitizers {
// exit_code: 123,
http_server: true,
});
+
+itest!(
+ node_process_beforeexit_exit_events_emitted_without_listeners {
+ args: "run node/process_beforeexit_exit_events.ts",
+ output: "node/process_beforeexit_exit_events.out",
+ exit_code: 0,
+ }
+);
+
+itest!(web_node_events_dispatched_in_correct_order {
+ args: "run node/events_order.ts",
+ output: "node/events_order.out",
+ exit_code: 0,
+});
diff --git a/tests/testdata/node/events_order.out b/tests/testdata/node/events_order.out
new file mode 100644
index 000000000..270384d8d
--- /dev/null
+++ b/tests/testdata/node/events_order.out
@@ -0,0 +1,12 @@
+beforeunload emitted from addEventListener
+beforeunload emitted from addEventListener
+beforeunload emitted from addEventListener
+beforeExit emitted from process.on
+more work done! 1
+beforeunload emitted from addEventListener
+beforeExit emitted from process.on
+more work done! 2
+beforeunload emitted from addEventListener
+beforeExit emitted from process.on
+unload emitted from addEventListener
+exit emitted from process.on
diff --git a/tests/testdata/node/events_order.ts b/tests/testdata/node/events_order.ts
new file mode 100644
index 000000000..263f46b4c
--- /dev/null
+++ b/tests/testdata/node/events_order.ts
@@ -0,0 +1,25 @@
+import process from "node:process";
+
+let count = 0;
+process.on("beforeExit", () => {
+ if (count === 0 || count === 1) {
+ setTimeout(() => console.log("more work done!", count), 10);
+ }
+ count++;
+ console.log("beforeExit emitted from process.on");
+});
+process.on("exit", () => console.log("exit emitted from process.on"));
+
+let countWeb = 0;
+addEventListener("beforeunload", (event) => {
+ if (countWeb == 0 || countWeb == 1) {
+ event.preventDefault();
+ }
+ countWeb++;
+ console.log("beforeunload emitted from addEventListener");
+});
+
+addEventListener(
+ "unload",
+ () => console.log("unload emitted from addEventListener"),
+);
diff --git a/tests/testdata/node/process_beforeexit_exit_events.out b/tests/testdata/node/process_beforeexit_exit_events.out
new file mode 100644
index 000000000..740ef6ffb
--- /dev/null
+++ b/tests/testdata/node/process_beforeexit_exit_events.out
@@ -0,0 +1,2 @@
+beforeExit emitted from processEmit
+exit emitted from processEmit
diff --git a/tests/testdata/node/process_beforeexit_exit_events.ts b/tests/testdata/node/process_beforeexit_exit_events.ts
new file mode 100644
index 000000000..a4c87f27e
--- /dev/null
+++ b/tests/testdata/node/process_beforeexit_exit_events.ts
@@ -0,0 +1,9 @@
+import process from "node:process";
+
+const originalEmit = process.emit;
+process.emit = function (event, ...args) {
+ if (event === "exit" || event === "beforeExit") {
+ console.log(`${event} emitted from processEmit`);
+ }
+ return originalEmit.call(this, event, ...args);
+};