seq_runtime/
signal.rs

1//! Signal handling API for Seq
2//!
3//! Provides Unix signal handling with a safe, flag-based approach:
4//! - Signals are trapped and set atomic flags (no code runs in signal context)
5//! - User code polls for signals at safe points
6//! - Fits Seq's explicit, predictable style
7//!
8//! # Example
9//! ```seq
10//! signal.SIGINT signal.trap
11//! signal.SIGTERM signal.trap
12//!
13//! : main-loop ( -- )
14//!   signal.SIGINT signal.received? if
15//!     "Shutting down..." io.write-line
16//!     return
17//!   then
18//!   do-work
19//!   main-loop
20//! ;
21//! ```
22//!
23//! # Safety
24//!
25//! Signal handlers execute in an interrupt context with severe restrictions.
26//! This module uses only async-signal-safe operations (atomic flag setting).
27//! All Seq code execution happens outside the signal handler, when the user
28//! explicitly checks for received signals.
29//!
30//! # Thread Safety and Concurrent Access
31//!
32//! This module is designed to be safe for concurrent use from multiple strands:
33//!
34//! - **Handler installation** (`signal.trap`, `signal.default`, `signal.ignore`):
35//!   Protected by a mutex to ensure only one strand modifies handlers at a time.
36//!   Concurrent calls will serialize safely.
37//!
38//! - **Flag operations** (`signal.received?`, `signal.pending?`, `signal.clear`):
39//!   Use lock-free atomic operations with appropriate memory ordering:
40//!   - `signal.received?`: Atomic swap with Acquire ordering (read-modify-write)
41//!   - `signal.pending?`: Atomic load with Acquire ordering (read-only)
42//!   - `signal.clear`: Atomic store with Release ordering (write-only)
43//!
44//!   Multiple strands can safely check the same signal. However, `signal.received?`
45//!   clears the flag atomically, so if two strands both call it, only one will
46//!   observe `true`. Use `signal.pending?` if you need non-destructive reads.
47//!
48//! - **Signal handler**: Executes outside the strand context (in OS interrupt
49//!   context) and only performs a single atomic store. This is async-signal-safe.
50//!
51//! This module uses `sigaction()` instead of the deprecated `signal()` function
52//! for well-defined behavior in multithreaded environments.
53//!
54//! # Platform Support
55//!
56//! - Unix: Full signal support using sigaction()
57//! - Windows: Stub implementations (signals not supported, all operations no-op)
58
59use crate::stack::{Stack, pop, push};
60use crate::value::Value;
61use std::sync::atomic::{AtomicBool, Ordering};
62
63/// Maximum signal number we support (covers all standard Unix signals)
64const MAX_SIGNAL: usize = 32;
65
66/// Atomic flags for each signal - set by signal handler, cleared by user code
67static SIGNAL_FLAGS: [AtomicBool; MAX_SIGNAL] = [
68    AtomicBool::new(false),
69    AtomicBool::new(false),
70    AtomicBool::new(false),
71    AtomicBool::new(false),
72    AtomicBool::new(false),
73    AtomicBool::new(false),
74    AtomicBool::new(false),
75    AtomicBool::new(false),
76    AtomicBool::new(false),
77    AtomicBool::new(false),
78    AtomicBool::new(false),
79    AtomicBool::new(false),
80    AtomicBool::new(false),
81    AtomicBool::new(false),
82    AtomicBool::new(false),
83    AtomicBool::new(false),
84    AtomicBool::new(false),
85    AtomicBool::new(false),
86    AtomicBool::new(false),
87    AtomicBool::new(false),
88    AtomicBool::new(false),
89    AtomicBool::new(false),
90    AtomicBool::new(false),
91    AtomicBool::new(false),
92    AtomicBool::new(false),
93    AtomicBool::new(false),
94    AtomicBool::new(false),
95    AtomicBool::new(false),
96    AtomicBool::new(false),
97    AtomicBool::new(false),
98    AtomicBool::new(false),
99    AtomicBool::new(false),
100];
101
102/// Mutex to protect signal handler installation from concurrent access
103#[cfg(unix)]
104static SIGNAL_MUTEX: std::sync::Mutex<()> = std::sync::Mutex::new(());
105
106/// Signal handler that just sets the atomic flag
107/// This is async-signal-safe: only uses atomic operations
108#[cfg(unix)]
109extern "C" fn flag_signal_handler(sig: libc::c_int) {
110    let sig_num = sig as usize;
111    if sig_num < MAX_SIGNAL {
112        SIGNAL_FLAGS[sig_num].store(true, Ordering::Release);
113    }
114}
115
116// ============================================================================
117// Signal Constants - Platform-correct values from libc
118// ============================================================================
119
120/// Get SIGINT constant (Ctrl+C interrupt)
121///
122/// Stack effect: ( -- Int )
123///
124/// # Safety
125/// Stack pointer must be valid.
126#[cfg(unix)]
127#[unsafe(no_mangle)]
128pub unsafe extern "C" fn patch_seq_signal_sigint(stack: Stack) -> Stack {
129    unsafe { push(stack, Value::Int(libc::SIGINT as i64)) }
130}
131
132/// Get SIGTERM constant (termination request)
133///
134/// Stack effect: ( -- Int )
135///
136/// # Safety
137/// Stack pointer must be valid.
138#[cfg(unix)]
139#[unsafe(no_mangle)]
140pub unsafe extern "C" fn patch_seq_signal_sigterm(stack: Stack) -> Stack {
141    unsafe { push(stack, Value::Int(libc::SIGTERM as i64)) }
142}
143
144/// Get SIGHUP constant (hangup)
145///
146/// Stack effect: ( -- Int )
147///
148/// # Safety
149/// Stack pointer must be valid.
150#[cfg(unix)]
151#[unsafe(no_mangle)]
152pub unsafe extern "C" fn patch_seq_signal_sighup(stack: Stack) -> Stack {
153    unsafe { push(stack, Value::Int(libc::SIGHUP as i64)) }
154}
155
156/// Get SIGPIPE constant (broken pipe)
157///
158/// Stack effect: ( -- Int )
159///
160/// # Safety
161/// Stack pointer must be valid.
162#[cfg(unix)]
163#[unsafe(no_mangle)]
164pub unsafe extern "C" fn patch_seq_signal_sigpipe(stack: Stack) -> Stack {
165    unsafe { push(stack, Value::Int(libc::SIGPIPE as i64)) }
166}
167
168/// Get SIGUSR1 constant (user signal 1)
169///
170/// Stack effect: ( -- Int )
171///
172/// # Safety
173/// Stack pointer must be valid.
174#[cfg(unix)]
175#[unsafe(no_mangle)]
176pub unsafe extern "C" fn patch_seq_signal_sigusr1(stack: Stack) -> Stack {
177    unsafe { push(stack, Value::Int(libc::SIGUSR1 as i64)) }
178}
179
180/// Get SIGUSR2 constant (user signal 2)
181///
182/// Stack effect: ( -- Int )
183///
184/// # Safety
185/// Stack pointer must be valid.
186#[cfg(unix)]
187#[unsafe(no_mangle)]
188pub unsafe extern "C" fn patch_seq_signal_sigusr2(stack: Stack) -> Stack {
189    unsafe { push(stack, Value::Int(libc::SIGUSR2 as i64)) }
190}
191
192/// Get SIGCHLD constant (child status change)
193///
194/// Stack effect: ( -- Int )
195///
196/// # Safety
197/// Stack pointer must be valid.
198#[cfg(unix)]
199#[unsafe(no_mangle)]
200pub unsafe extern "C" fn patch_seq_signal_sigchld(stack: Stack) -> Stack {
201    unsafe { push(stack, Value::Int(libc::SIGCHLD as i64)) }
202}
203
204/// Get SIGALRM constant (alarm clock)
205///
206/// Stack effect: ( -- Int )
207///
208/// # Safety
209/// Stack pointer must be valid.
210#[cfg(unix)]
211#[unsafe(no_mangle)]
212pub unsafe extern "C" fn patch_seq_signal_sigalrm(stack: Stack) -> Stack {
213    unsafe { push(stack, Value::Int(libc::SIGALRM as i64)) }
214}
215
216/// Get SIGCONT constant (continue)
217///
218/// Stack effect: ( -- Int )
219///
220/// # Safety
221/// Stack pointer must be valid.
222#[cfg(unix)]
223#[unsafe(no_mangle)]
224pub unsafe extern "C" fn patch_seq_signal_sigcont(stack: Stack) -> Stack {
225    unsafe { push(stack, Value::Int(libc::SIGCONT as i64)) }
226}
227
228// Non-Unix stubs for signal constants (return 0)
229// Safety: Stack pointer must be valid for all functions below.
230
231/// # Safety
232/// Stack pointer must be valid.
233#[cfg(not(unix))]
234#[unsafe(no_mangle)]
235pub unsafe extern "C" fn patch_seq_signal_sigint(stack: Stack) -> Stack {
236    unsafe { push(stack, Value::Int(0)) }
237}
238
239/// # Safety
240/// Stack pointer must be valid.
241#[cfg(not(unix))]
242#[unsafe(no_mangle)]
243pub unsafe extern "C" fn patch_seq_signal_sigterm(stack: Stack) -> Stack {
244    unsafe { push(stack, Value::Int(0)) }
245}
246
247/// # Safety
248/// Stack pointer must be valid.
249#[cfg(not(unix))]
250#[unsafe(no_mangle)]
251pub unsafe extern "C" fn patch_seq_signal_sighup(stack: Stack) -> Stack {
252    unsafe { push(stack, Value::Int(0)) }
253}
254
255/// # Safety
256/// Stack pointer must be valid.
257#[cfg(not(unix))]
258#[unsafe(no_mangle)]
259pub unsafe extern "C" fn patch_seq_signal_sigpipe(stack: Stack) -> Stack {
260    unsafe { push(stack, Value::Int(0)) }
261}
262
263/// # Safety
264/// Stack pointer must be valid.
265#[cfg(not(unix))]
266#[unsafe(no_mangle)]
267pub unsafe extern "C" fn patch_seq_signal_sigusr1(stack: Stack) -> Stack {
268    unsafe { push(stack, Value::Int(0)) }
269}
270
271/// # Safety
272/// Stack pointer must be valid.
273#[cfg(not(unix))]
274#[unsafe(no_mangle)]
275pub unsafe extern "C" fn patch_seq_signal_sigusr2(stack: Stack) -> Stack {
276    unsafe { push(stack, Value::Int(0)) }
277}
278
279/// # Safety
280/// Stack pointer must be valid.
281#[cfg(not(unix))]
282#[unsafe(no_mangle)]
283pub unsafe extern "C" fn patch_seq_signal_sigchld(stack: Stack) -> Stack {
284    unsafe { push(stack, Value::Int(0)) }
285}
286
287/// # Safety
288/// Stack pointer must be valid.
289#[cfg(not(unix))]
290#[unsafe(no_mangle)]
291pub unsafe extern "C" fn patch_seq_signal_sigalrm(stack: Stack) -> Stack {
292    unsafe { push(stack, Value::Int(0)) }
293}
294
295/// # Safety
296/// Stack pointer must be valid.
297#[cfg(not(unix))]
298#[unsafe(no_mangle)]
299pub unsafe extern "C" fn patch_seq_signal_sigcont(stack: Stack) -> Stack {
300    unsafe { push(stack, Value::Int(0)) }
301}
302
303/// Install a signal handler using sigaction (thread-safe)
304///
305/// Uses sigaction() instead of signal() for:
306/// - Well-defined semantics across platforms
307/// - Thread safety with strands
308/// - SA_RESTART to automatically restart interrupted syscalls
309#[cfg(unix)]
310fn install_signal_handler(sig_num: libc::c_int) -> Result<(), std::io::Error> {
311    use std::mem::MaybeUninit;
312
313    let _guard = SIGNAL_MUTEX
314        .lock()
315        .expect("signal: mutex poisoned during handler installation");
316
317    unsafe {
318        let mut action: libc::sigaction = MaybeUninit::zeroed().assume_init();
319        // Use sa_handler (not sa_sigaction) since we're not using SA_SIGINFO
320        action.sa_sigaction = flag_signal_handler as libc::sighandler_t;
321        action.sa_flags = libc::SA_RESTART;
322        libc::sigemptyset(&mut action.sa_mask);
323
324        let result = libc::sigaction(sig_num, &action, std::ptr::null_mut());
325        if result != 0 {
326            return Err(std::io::Error::last_os_error());
327        }
328    }
329    Ok(())
330}
331
332/// Restore default signal handler using sigaction (thread-safe)
333#[cfg(unix)]
334fn restore_default_handler(sig_num: libc::c_int) -> Result<(), std::io::Error> {
335    use std::mem::MaybeUninit;
336
337    let _guard = SIGNAL_MUTEX
338        .lock()
339        .expect("signal: mutex poisoned during handler restoration");
340
341    unsafe {
342        let mut action: libc::sigaction = MaybeUninit::zeroed().assume_init();
343        // Use SIG_DFL to restore default handler
344        action.sa_sigaction = libc::SIG_DFL as libc::sighandler_t;
345        action.sa_flags = 0;
346        libc::sigemptyset(&mut action.sa_mask);
347
348        let result = libc::sigaction(sig_num, &action, std::ptr::null_mut());
349        if result != 0 {
350            return Err(std::io::Error::last_os_error());
351        }
352    }
353    Ok(())
354}
355
356/// Ignore a signal using sigaction (thread-safe)
357#[cfg(unix)]
358fn ignore_signal(sig_num: libc::c_int) -> Result<(), std::io::Error> {
359    use std::mem::MaybeUninit;
360
361    let _guard = SIGNAL_MUTEX
362        .lock()
363        .expect("signal: mutex poisoned during ignore");
364
365    unsafe {
366        let mut action: libc::sigaction = MaybeUninit::zeroed().assume_init();
367        // Use SIG_IGN to ignore the signal
368        action.sa_sigaction = libc::SIG_IGN as libc::sighandler_t;
369        action.sa_flags = 0;
370        libc::sigemptyset(&mut action.sa_mask);
371
372        let result = libc::sigaction(sig_num, &action, std::ptr::null_mut());
373        if result != 0 {
374            return Err(std::io::Error::last_os_error());
375        }
376    }
377    Ok(())
378}
379
380/// Trap a signal: install handler that sets flag instead of default behavior
381///
382/// Stack effect: ( signal-num -- )
383///
384/// After trapping, the signal will set an internal flag instead of its default
385/// action (which might be to terminate the process). Use `signal.received?` to
386/// check and clear the flag.
387///
388/// # Safety
389/// Stack must have an Int (signal number) on top
390#[cfg(unix)]
391#[unsafe(no_mangle)]
392pub unsafe extern "C" fn patch_seq_signal_trap(stack: Stack) -> Stack {
393    unsafe {
394        let (stack, sig_val) = pop(stack);
395        let sig_num = match sig_val {
396            Value::Int(n) => {
397                if n < 0 || n as usize >= MAX_SIGNAL {
398                    panic!("signal.trap: invalid signal number {}", n);
399                }
400                n as libc::c_int
401            }
402            _ => panic!(
403                "signal.trap: expected Int (signal number), got {:?}",
404                sig_val
405            ),
406        };
407
408        // Install our flag-setting handler using sigaction
409        if let Err(e) = install_signal_handler(sig_num) {
410            panic!(
411                "signal.trap: failed to install handler for signal {}: {}",
412                sig_num, e
413            );
414        }
415        stack
416    }
417}
418
419/// Check if a signal was received and clear the flag
420///
421/// Stack effect: ( signal-num -- received? )
422///
423/// Returns true if the signal was received since the last check, false otherwise.
424/// This atomically clears the flag, so the signal must be received again to return true.
425///
426/// # Safety
427/// Stack must have an Int (signal number) on top
428#[cfg(unix)]
429#[unsafe(no_mangle)]
430pub unsafe extern "C" fn patch_seq_signal_received(stack: Stack) -> Stack {
431    unsafe {
432        let (stack, sig_val) = pop(stack);
433        let sig_num = match sig_val {
434            Value::Int(n) => {
435                if n < 0 || n as usize >= MAX_SIGNAL {
436                    panic!("signal.received?: invalid signal number {}", n);
437                }
438                n as usize
439            }
440            _ => panic!(
441                "signal.received?: expected Int (signal number), got {:?}",
442                sig_val
443            ),
444        };
445
446        // Atomically swap the flag to false and return the old value
447        let was_set = SIGNAL_FLAGS[sig_num].swap(false, Ordering::Acquire);
448        push(stack, Value::Bool(was_set))
449    }
450}
451
452/// Check if a signal is pending without clearing the flag
453///
454/// Stack effect: ( signal-num -- pending? )
455///
456/// Returns true if the signal was received, false otherwise.
457/// Unlike `signal.received?`, this does NOT clear the flag.
458///
459/// # Safety
460/// Stack must have an Int (signal number) on top
461#[cfg(unix)]
462#[unsafe(no_mangle)]
463pub unsafe extern "C" fn patch_seq_signal_pending(stack: Stack) -> Stack {
464    unsafe {
465        let (stack, sig_val) = pop(stack);
466        let sig_num = match sig_val {
467            Value::Int(n) => {
468                if n < 0 || n as usize >= MAX_SIGNAL {
469                    panic!("signal.pending?: invalid signal number {}", n);
470                }
471                n as usize
472            }
473            _ => panic!(
474                "signal.pending?: expected Int (signal number), got {:?}",
475                sig_val
476            ),
477        };
478
479        let is_set = SIGNAL_FLAGS[sig_num].load(Ordering::Acquire);
480        push(stack, Value::Bool(is_set))
481    }
482}
483
484/// Restore the default handler for a signal
485///
486/// Stack effect: ( signal-num -- )
487///
488/// Restores the system default behavior for the signal.
489///
490/// # Safety
491/// Stack must have an Int (signal number) on top
492#[cfg(unix)]
493#[unsafe(no_mangle)]
494pub unsafe extern "C" fn patch_seq_signal_default(stack: Stack) -> Stack {
495    unsafe {
496        let (stack, sig_val) = pop(stack);
497        let sig_num = match sig_val {
498            Value::Int(n) => {
499                if n < 0 || n as usize >= MAX_SIGNAL {
500                    panic!("signal.default: invalid signal number {}", n);
501                }
502                n as libc::c_int
503            }
504            _ => panic!(
505                "signal.default: expected Int (signal number), got {:?}",
506                sig_val
507            ),
508        };
509
510        if let Err(e) = restore_default_handler(sig_num) {
511            panic!(
512                "signal.default: failed to restore default handler for signal {}: {}",
513                sig_num, e
514            );
515        }
516        stack
517    }
518}
519
520/// Ignore a signal entirely
521///
522/// Stack effect: ( signal-num -- )
523///
524/// The signal will be ignored - it won't terminate the process or set any flag.
525/// Useful for SIGPIPE in network servers.
526///
527/// # Safety
528/// Stack must have an Int (signal number) on top
529#[cfg(unix)]
530#[unsafe(no_mangle)]
531pub unsafe extern "C" fn patch_seq_signal_ignore(stack: Stack) -> Stack {
532    unsafe {
533        let (stack, sig_val) = pop(stack);
534        let sig_num = match sig_val {
535            Value::Int(n) => {
536                if n < 0 || n as usize >= MAX_SIGNAL {
537                    panic!("signal.ignore: invalid signal number {}", n);
538                }
539                n as libc::c_int
540            }
541            _ => panic!(
542                "signal.ignore: expected Int (signal number), got {:?}",
543                sig_val
544            ),
545        };
546
547        if let Err(e) = ignore_signal(sig_num) {
548            panic!("signal.ignore: failed to ignore signal {}: {}", sig_num, e);
549        }
550        stack
551    }
552}
553
554/// Clear the flag for a signal without checking it
555///
556/// Stack effect: ( signal-num -- )
557///
558/// Useful for resetting state without caring about the previous value.
559///
560/// # Safety
561/// Stack must have an Int (signal number) on top
562#[cfg(unix)]
563#[unsafe(no_mangle)]
564pub unsafe extern "C" fn patch_seq_signal_clear(stack: Stack) -> Stack {
565    unsafe {
566        let (stack, sig_val) = pop(stack);
567        let sig_num = match sig_val {
568            Value::Int(n) => {
569                if n < 0 || n as usize >= MAX_SIGNAL {
570                    panic!("signal.clear: invalid signal number {}", n);
571                }
572                n as usize
573            }
574            _ => panic!(
575                "signal.clear: expected Int (signal number), got {:?}",
576                sig_val
577            ),
578        };
579
580        SIGNAL_FLAGS[sig_num].store(false, Ordering::Release);
581        stack
582    }
583}
584
585// Stub implementations for non-Unix platforms
586// Safety: Stack pointer must be valid for all functions below.
587
588/// # Safety
589/// Stack pointer must be valid.
590#[cfg(not(unix))]
591#[unsafe(no_mangle)]
592pub unsafe extern "C" fn patch_seq_signal_trap(stack: Stack) -> Stack {
593    let (stack, _) = unsafe { pop(stack) };
594    // No-op on non-Unix - signals not supported
595    stack
596}
597
598/// # Safety
599/// Stack pointer must be valid.
600#[cfg(not(unix))]
601#[unsafe(no_mangle)]
602pub unsafe extern "C" fn patch_seq_signal_default(stack: Stack) -> Stack {
603    let (stack, _) = unsafe { pop(stack) };
604    stack
605}
606
607/// # Safety
608/// Stack pointer must be valid.
609#[cfg(not(unix))]
610#[unsafe(no_mangle)]
611pub unsafe extern "C" fn patch_seq_signal_ignore(stack: Stack) -> Stack {
612    let (stack, _) = unsafe { pop(stack) };
613    stack
614}
615
616/// # Safety
617/// Stack pointer must be valid.
618#[cfg(not(unix))]
619#[unsafe(no_mangle)]
620pub unsafe extern "C" fn patch_seq_signal_received(stack: Stack) -> Stack {
621    let (stack, _) = unsafe { pop(stack) };
622    // Always return false on non-Unix - signals not supported
623    unsafe { push(stack, Value::Bool(false)) }
624}
625
626/// # Safety
627/// Stack pointer must be valid.
628#[cfg(not(unix))]
629#[unsafe(no_mangle)]
630pub unsafe extern "C" fn patch_seq_signal_pending(stack: Stack) -> Stack {
631    let (stack, _) = unsafe { pop(stack) };
632    // Always return false on non-Unix - signals not supported
633    unsafe { push(stack, Value::Bool(false)) }
634}
635
636/// # Safety
637/// Stack pointer must be valid.
638#[cfg(not(unix))]
639#[unsafe(no_mangle)]
640pub unsafe extern "C" fn patch_seq_signal_clear(stack: Stack) -> Stack {
641    let (stack, _) = unsafe { pop(stack) };
642    // No-op on non-Unix
643    stack
644}
645
646#[cfg(test)]
647mod tests {
648    use super::*;
649    use serial_test::serial;
650
651    #[test]
652    #[serial]
653    fn test_signal_flag_operations() {
654        // Use index 3 (SIGQUIT) to avoid conflicts with actual signal tests
655        // Tests use: SIGUSR1 (10 Linux, 30 macOS), SIGUSR2 (12 Linux, 31 macOS)
656        // Index 3 is safe on both platforms
657        const TEST_IDX: usize = 3;
658
659        // Clear flag first (other tests might have set it)
660        SIGNAL_FLAGS[TEST_IDX].store(false, Ordering::Release);
661
662        // Now test that flag is false
663        assert!(!SIGNAL_FLAGS[TEST_IDX].load(Ordering::Acquire));
664
665        // Set flag manually (simulating signal receipt)
666        SIGNAL_FLAGS[TEST_IDX].store(true, Ordering::Release);
667        assert!(SIGNAL_FLAGS[TEST_IDX].load(Ordering::Acquire));
668
669        // Swap should return old value and set new
670        let was_set = SIGNAL_FLAGS[TEST_IDX].swap(false, Ordering::Acquire);
671        assert!(was_set);
672        assert!(!SIGNAL_FLAGS[TEST_IDX].load(Ordering::Acquire));
673
674        // Second swap should return false
675        let was_set = SIGNAL_FLAGS[TEST_IDX].swap(false, Ordering::Acquire);
676        assert!(!was_set);
677
678        // Clean up
679        SIGNAL_FLAGS[TEST_IDX].store(false, Ordering::Release);
680    }
681
682    #[cfg(unix)]
683    #[test]
684    #[serial]
685    fn test_signal_handler_installation() {
686        // Test that we can install a handler for SIGUSR1 (safe for testing)
687        let result = install_signal_handler(libc::SIGUSR1);
688        assert!(result.is_ok(), "Failed to install SIGUSR1 handler");
689
690        // Test that we can restore the default handler
691        let result = restore_default_handler(libc::SIGUSR1);
692        assert!(result.is_ok(), "Failed to restore SIGUSR1 default handler");
693    }
694
695    #[cfg(unix)]
696    #[test]
697    #[serial]
698    fn test_signal_delivery() {
699        // Install handler for SIGUSR1
700        install_signal_handler(libc::SIGUSR1).expect("Failed to install handler");
701
702        // Clear any pending flag
703        SIGNAL_FLAGS[libc::SIGUSR1 as usize].store(false, Ordering::Release);
704
705        // Send signal to self
706        unsafe {
707            libc::kill(libc::getpid(), libc::SIGUSR1);
708        }
709
710        // Give a tiny bit of time for signal delivery (should be immediate)
711        std::thread::sleep(std::time::Duration::from_millis(1));
712
713        // Check that the flag was set
714        let received = SIGNAL_FLAGS[libc::SIGUSR1 as usize].swap(false, Ordering::Acquire);
715        assert!(received, "Signal was not received");
716
717        // Restore default handler
718        restore_default_handler(libc::SIGUSR1).expect("Failed to restore handler");
719    }
720
721    #[cfg(unix)]
722    #[test]
723    #[serial]
724    fn test_invalid_signal_fails() {
725        // SIGKILL and SIGSTOP cannot be caught
726        let result = install_signal_handler(libc::SIGKILL);
727        assert!(result.is_err(), "SIGKILL should not be catchable");
728
729        let result = install_signal_handler(libc::SIGSTOP);
730        assert!(result.is_err(), "SIGSTOP should not be catchable");
731    }
732
733    #[cfg(unix)]
734    #[test]
735    #[serial]
736    fn test_signal_ignore() {
737        // Test that we can set a signal to be ignored
738        let result = ignore_signal(libc::SIGUSR2);
739        assert!(result.is_ok(), "Failed to ignore SIGUSR2");
740
741        // Restore default
742        let result = restore_default_handler(libc::SIGUSR2);
743        assert!(result.is_ok(), "Failed to restore SIGUSR2 default handler");
744    }
745
746    #[cfg(unix)]
747    #[test]
748    #[serial]
749    fn test_multiple_signals_independent() {
750        // Install handlers for two different signals
751        install_signal_handler(libc::SIGUSR1).expect("Failed to install SIGUSR1");
752        install_signal_handler(libc::SIGUSR2).expect("Failed to install SIGUSR2");
753
754        // Clear flags
755        SIGNAL_FLAGS[libc::SIGUSR1 as usize].store(false, Ordering::Release);
756        SIGNAL_FLAGS[libc::SIGUSR2 as usize].store(false, Ordering::Release);
757
758        // Send only SIGUSR1
759        unsafe {
760            libc::kill(libc::getpid(), libc::SIGUSR1);
761        }
762        std::thread::sleep(std::time::Duration::from_millis(1));
763
764        // Only SIGUSR1 should be set
765        assert!(SIGNAL_FLAGS[libc::SIGUSR1 as usize].load(Ordering::Acquire));
766        assert!(!SIGNAL_FLAGS[libc::SIGUSR2 as usize].load(Ordering::Acquire));
767
768        // Cleanup
769        restore_default_handler(libc::SIGUSR1).expect("Failed to restore SIGUSR1");
770        restore_default_handler(libc::SIGUSR2).expect("Failed to restore SIGUSR2");
771    }
772
773    #[cfg(unix)]
774    #[test]
775    fn test_signal_constants_valid() {
776        // Verify signal constants are in valid range
777        assert!(libc::SIGINT > 0 && (libc::SIGINT as usize) < MAX_SIGNAL);
778        assert!(libc::SIGTERM > 0 && (libc::SIGTERM as usize) < MAX_SIGNAL);
779        assert!(libc::SIGHUP > 0 && (libc::SIGHUP as usize) < MAX_SIGNAL);
780        assert!(libc::SIGPIPE > 0 && (libc::SIGPIPE as usize) < MAX_SIGNAL);
781        assert!(libc::SIGUSR1 > 0 && (libc::SIGUSR1 as usize) < MAX_SIGNAL);
782        assert!(libc::SIGUSR2 > 0 && (libc::SIGUSR2 as usize) < MAX_SIGNAL);
783    }
784}