summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock15
-rw-r--r--runtime/Cargo.toml1
-rw-r--r--runtime/ops/signal.rs76
-rw-r--r--tests/unit/signal_test.ts10
-rw-r--r--tests/unit_node/process_test.ts7
5 files changed, 102 insertions, 7 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 3a062f823..960f5f04d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -87,9 +87,9 @@ dependencies = [
[[package]]
name = "ahash"
-version = "0.8.6"
+version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a"
+checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [
"cfg-if",
"getrandom",
@@ -1781,6 +1781,7 @@ dependencies = [
"ring",
"rustyline",
"serde",
+ "signal-hook",
"signal-hook-registry",
"test_server",
"tokio",
@@ -5663,6 +5664,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f"
[[package]]
+name = "signal-hook"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
+dependencies = [
+ "libc",
+ "signal-hook-registry",
+]
+
+[[package]]
name = "signal-hook-registry"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml
index 856643dba..723a4b2a9 100644
--- a/runtime/Cargo.toml
+++ b/runtime/Cargo.toml
@@ -119,6 +119,7 @@ regex.workspace = true
ring.workspace = true
rustyline = { workspace = true, features = ["custom-bindings"] }
serde.workspace = true
+signal-hook = "0.3.17"
signal-hook-registry = "1.4.0"
tokio.workspace = true
tokio-metrics.workspace = true
diff --git a/runtime/ops/signal.rs b/runtime/ops/signal.rs
index 01a07e52a..5aaf1ddb2 100644
--- a/runtime/ops/signal.rs
+++ b/runtime/ops/signal.rs
@@ -12,7 +12,13 @@ use deno_core::ResourceId;
use std::borrow::Cow;
use std::cell::RefCell;
+#[cfg(unix)]
+use std::collections::BTreeMap;
use std::rc::Rc;
+#[cfg(unix)]
+use std::sync::atomic::AtomicBool;
+#[cfg(unix)]
+use std::sync::Arc;
#[cfg(unix)]
use tokio::signal::unix::signal;
@@ -32,13 +38,52 @@ use tokio::signal::windows::CtrlC;
deno_core::extension!(
deno_signal,
ops = [op_signal_bind, op_signal_unbind, op_signal_poll],
+ state = |state| {
+ #[cfg(unix)]
+ {
+ state.put(SignalState::default());
+ }
+ }
);
#[cfg(unix)]
+#[derive(Default)]
+struct SignalState {
+ enable_default_handlers: BTreeMap<libc::c_int, Arc<AtomicBool>>,
+}
+
+#[cfg(unix)]
+impl SignalState {
+ /// Disable the default signal handler for the given signal.
+ ///
+ /// Returns the shared flag to enable the default handler later, and whether a default handler already existed.
+ fn disable_default_handler(
+ &mut self,
+ signo: libc::c_int,
+ ) -> (Arc<AtomicBool>, bool) {
+ use std::collections::btree_map::Entry;
+
+ match self.enable_default_handlers.entry(signo) {
+ Entry::Occupied(entry) => {
+ let enable = entry.get();
+ enable.store(false, std::sync::atomic::Ordering::Release);
+ (enable.clone(), true)
+ }
+ Entry::Vacant(entry) => {
+ let enable = Arc::new(AtomicBool::new(false));
+ entry.insert(enable.clone());
+ (enable, false)
+ }
+ }
+ }
+}
+
+#[cfg(unix)]
/// The resource for signal stream.
/// The second element is the waker of polling future.
struct SignalStreamResource {
signal: AsyncRefCell<Signal>,
+ enable_default_handler: Arc<AtomicBool>,
cancel: CancelHandle,
}
@@ -548,11 +593,29 @@ fn op_signal_bind(
"Binding to signal '{sig}' is not allowed",
)));
}
+
+ let signal = AsyncRefCell::new(signal(SignalKind::from_raw(signo))?);
+
+ let (enable_default_handler, has_default_handler) = state
+ .borrow_mut::<SignalState>()
+ .disable_default_handler(signo);
+
let resource = SignalStreamResource {
- signal: AsyncRefCell::new(signal(SignalKind::from_raw(signo))?),
+ signal,
cancel: Default::default(),
+ enable_default_handler: enable_default_handler.clone(),
};
let rid = state.resource_table.add(resource);
+
+ if !has_default_handler {
+ // restore default signal handler when the signal is unbound
+ // this can error if the signal is not supported, if so let's just leave it as is
+ let _ = signal_hook::flag::register_conditional_default(
+ signo,
+ enable_default_handler,
+ );
+ }
+
Ok(rid)
}
@@ -606,6 +669,15 @@ pub fn op_signal_unbind(
state: &mut OpState,
#[smi] rid: ResourceId,
) -> Result<(), AnyError> {
- state.resource_table.take_any(rid)?.close();
+ let resource = state.resource_table.take::<SignalStreamResource>(rid)?;
+
+ #[cfg(unix)]
+ {
+ resource
+ .enable_default_handler
+ .store(true, std::sync::atomic::Ordering::Release);
+ }
+
+ resource.close();
Ok(())
}
diff --git a/tests/unit/signal_test.ts b/tests/unit/signal_test.ts
index 2ba2ffb15..1d9b10ae7 100644
--- a/tests/unit/signal_test.ts
+++ b/tests/unit/signal_test.ts
@@ -172,10 +172,20 @@ Deno.test(
}
// Sends SIGUSR1 (irrelevant signal) 3 times.
+ // By default SIGUSR1 terminates, so set it to a no-op for this test.
+ let count = 0;
+ const irrelevant = () => {
+ count++;
+ };
+ Deno.addSignalListener("SIGUSR1", irrelevant);
for (const _ of Array(3)) {
await delay(20);
Deno.kill(Deno.pid, "SIGUSR1");
}
+ while (count < 3) {
+ await delay(20);
+ }
+ Deno.removeSignalListener("SIGUSR1", irrelevant);
// No change
assertEquals(c, "010101000");
diff --git a/tests/unit_node/process_test.ts b/tests/unit_node/process_test.ts
index a28e1e5cd..f496290d9 100644
--- a/tests/unit_node/process_test.ts
+++ b/tests/unit_node/process_test.ts
@@ -237,7 +237,7 @@ Deno.test({
Deno.test({
name: "process.off signal",
- ignore: true, // This test fails to terminate
+ ignore: Deno.build.os == "windows",
async fn() {
const testTimeout = setTimeout(() => fail("Test timed out"), 10_000);
try {
@@ -246,13 +246,13 @@ Deno.test({
"eval",
`
import process from "node:process";
- console.log("ready");
setInterval(() => {}, 1000);
const listener = () => {
+ process.off("SIGINT", listener);
console.log("foo");
- process.off("SIGINT", listener)
};
process.on("SIGINT", listener);
+ console.log("ready");
`,
],
stdout: "piped",
@@ -275,6 +275,7 @@ Deno.test({
while (!output.includes("foo\n")) {
await delay(10);
}
+ process.kill("SIGINT");
await process.status;
} finally {
clearTimeout(testTimeout);