Skip to content

Commit 387013c

Browse files
committed
fix(pty_terminal): use signalfd for SIGINT handling on Linux
Replace signal_hook with nix::sys::signalfd::SignalFd in the send_ctrl_c_interrupts_process test on Linux. signalfd reads signals via a file descriptor without signal handlers or background threads, avoiding the musl .init_array deadlock where ctrlc's thread gets blocked by musl's internal lock. On macOS/Windows, keep using the ctrlc crate (no musl issues there).
1 parent 4443308 commit 387013c

File tree

3 files changed

+35
-42
lines changed

3 files changed

+35
-42
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ jsonc-parser = { version = "0.29.0", features = ["serde"] }
8585
libc = "0.2.172"
8686
memmap2 = "0.9.7"
8787
monostate = "1.0.2"
88-
nix = { version = "0.30.1", features = ["dir"] }
88+
nix = { version = "0.30.1", features = ["dir", "signal"] }
8989
ntapi = "0.4.1"
9090
nucleo-matcher = "0.3.1"
9191
once_cell = "1.19"

crates/pty_terminal/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ subprocess_test = { workspace = true, features = ["portable-pty"] }
2020
terminal_size = "0.4"
2121

2222
[target.'cfg(unix)'.dev-dependencies]
23+
nix = { workspace = true }
2324
signal-hook = "0.3"
2425

2526
[lints]

crates/pty_terminal/tests/terminal.rs

Lines changed: 33 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -275,60 +275,52 @@ fn send_ctrl_c_interrupts_process() {
275275
let cmd = CommandBuilder::from(command_for_fn!((), |(): ()| {
276276
use std::io::{Write, stdout};
277277

278-
// On Unix, use signal_hook directly instead of ctrlc.
279-
// ctrlc spawns a background thread to monitor signals, but the subprocess
280-
// closure runs during .init_array (via ctor). On musl, newly-created threads
281-
// cannot execute during init (musl holds a lock), so ctrlc's thread never
282-
// runs and SIGINT is silently swallowed.
283-
// signal_hook::low_level::register installs a raw signal handler with no
284-
// background thread, avoiding the issue entirely.
285-
#[cfg(unix)]
278+
// On Linux, use signalfd to wait for SIGINT without signal handlers or
279+
// background threads. This avoids musl issues where threads spawned during
280+
// .init_array (via ctor) are blocked by musl's internal lock.
281+
#[cfg(target_os = "linux")]
286282
{
287-
use std::sync::{
288-
Arc,
289-
atomic::{AtomicBool, Ordering},
283+
use nix::sys::{
284+
signal::{SigSet, Signal},
285+
signalfd::SignalFd,
290286
};
291287

292-
let interrupted = Arc::new(AtomicBool::new(false));
293-
let flag = Arc::clone(&interrupted);
288+
// Block SIGINT so it goes to signalfd instead of the default handler.
289+
let mut mask = SigSet::empty();
290+
mask.add(Signal::SIGINT);
291+
mask.thread_block().unwrap();
294292

295-
// SAFETY: The closure only performs an atomic store, which is signal-safe.
296-
unsafe {
297-
signal_hook::low_level::register(signal_hook::consts::SIGINT, move || {
298-
flag.store(true, Ordering::SeqCst);
299-
})
300-
.unwrap();
301-
}
293+
let sfd = SignalFd::new(&mask).unwrap();
302294

303295
println!("ready");
304296
stdout().flush().unwrap();
305297

306-
loop {
307-
if interrupted.load(Ordering::SeqCst) {
308-
print!("INTERRUPTED");
309-
stdout().flush().unwrap();
310-
std::process::exit(0);
311-
}
312-
std::thread::yield_now();
313-
}
298+
// Block until SIGINT arrives via signalfd.
299+
sfd.read_signal().unwrap().unwrap();
300+
print!("INTERRUPTED");
301+
stdout().flush().unwrap();
302+
std::process::exit(0);
314303
}
315304

316-
// On Windows, ctrlc works fine (no .init_array/musl issue).
317-
#[cfg(windows)]
305+
// On macOS/Windows, use ctrlc which works fine (no .init_array/musl issue).
306+
#[cfg(not(target_os = "linux"))]
318307
{
319-
// Clear the "ignore CTRL_C" flag set by Rust runtime
308+
// On Windows, clear the "ignore CTRL_C" flag set by Rust runtime
320309
// so that CTRL_C_EVENT reaches the ctrlc handler.
321-
// SAFETY: Declaring correct signature for SetConsoleCtrlHandler from kernel32.
322-
unsafe extern "system" {
323-
fn SetConsoleCtrlHandler(
324-
handler: Option<unsafe extern "system" fn(u32) -> i32>,
325-
add: i32,
326-
) -> i32;
327-
}
310+
#[cfg(windows)]
311+
{
312+
// SAFETY: Declaring correct signature for SetConsoleCtrlHandler from kernel32.
313+
unsafe extern "system" {
314+
fn SetConsoleCtrlHandler(
315+
handler: Option<unsafe extern "system" fn(u32) -> i32>,
316+
add: i32,
317+
) -> i32;
318+
}
328319

329-
// SAFETY: Clearing the "ignore CTRL_C" flag so handlers are invoked.
330-
unsafe {
331-
SetConsoleCtrlHandler(None, 0); // FALSE = remove ignore
320+
// SAFETY: Clearing the "ignore CTRL_C" flag so handlers are invoked.
321+
unsafe {
322+
SetConsoleCtrlHandler(None, 0); // FALSE = remove ignore
323+
}
332324
}
333325

334326
ctrlc::set_handler(move || {

0 commit comments

Comments
 (0)