Sending a channel message from a signal handler

I'm trying to add a signal (for SIGCHLD and SIGUSR1) notification channel for my Linux tool. I've figured that I'll just set up an always open communication channel, and just send a notification from my own signal handler. Later, I will spin up a loop and I'll wait for notifications on this channel (not sure if that's a valid approach?).

So I'm doing something like this:

use std::sync::mpsc::channel;
use std::sync::mpsc::{Receiver, Sender};
use std::thread;
use std::time::Duration;
use lazy_static::lazy_static;
use nix::sys::signal;
use nix::sys::signal::SigHandler::Handler;
use nix::sys::signal::Signal;
use nix::unistd::Pid;
use std::sync::Mutex;
  
lazy_static! {
    pub static ref SENDER: Mutex<Option<Sender<i32>>> = Mutex::new(None);
    pub static ref RECEIVER: Mutex<Option<Receiver<i32>>> = Mutex::new(None);
}
  
extern fn signal_handler(id: i32) {
    SENDER.lock().unwrap().as_ref().unwrap().send(id).unwrap();
}
  
fn main() {
    // Set up communication channel.
    let (tx, rx) = channel();
    *SENDER.lock().unwrap() = Some(tx);
    *RECEIVER.lock().unwrap() = Some(rx);
  
    // Set up signal handler.
    let sa = signal::SigAction::new(
        Handler(signal_handler),
        signal::SaFlags::empty(),
        signal::SigSet::empty());
  
    unsafe { signal::sigaction(Signal::SIGUSR1, &sa) }.unwrap();
    unsafe { signal::sigaction(Signal::SIGCHLD, &sa) }.unwrap();
  
    loop {
        println!("Waiting for message, pid {}...", Pid::this());
        let msg = {
            let lock = RECEIVER.lock().unwrap();
            lock.as_ref().unwrap().recv()
        };
        println!("Got message: {:?}", msg);
    }
}

The problem is that it doesn't work. Or, maybe in other words, it doesn't work when I compile it on rust 1.67 and above. The recv() function blocks indefinitely, and doesn't un-block when I send a signal to the tool.

But, when I compile it under rust 1.66, it works properly (or so it seems):

Waiting for message, pid 102986...
Got message: Ok(10)
Waiting for message, pid 102986...
Got message: Ok(10)
Waiting for message, pid 102986...
Got message: Ok(10)
Waiting for message, pid 102986...
Got message: Ok(17)
Waiting for message, pid 102986...
Got message: Ok(17)
Waiting for message, pid 102986...
Got message: Ok(17)
Waiting for message, pid 102986...

Does anyone have any idea what could be the reason why this code works on 1.66 and doesn't work on 1.67 and above (up to 1.71)? I'm using unsafe so I guess I could maybe hit some undefined behavior somewhere?

Maybe sending and receiving messages through one channel isn't supported when I'm trying to do it from the same thread? The problem goes away when I wrap the contents of the signal_handler with an std::thread::spawn(), and send the notification message from another thread. Is forcefully splitting my tool to multiple threads my only solution?

Very few operations are safe to perform in a signal handler: signal-safety(7) - Linux manual page.

You'll probably be better off working with an existing library that properly implements the handler like signal_hook - Rust.

4 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.