1

I'm trying to programmatically listen to multiple signals and a ticking interval. To clear things up, here is the code I have currently:

use std::time::Duration;

use tokio::signal::unix::SignalKind;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut interval = tokio::time::interval(Duration::from_secs(5));

    let sigrtmin = libc::SIGRTMIN();
    let program_name = std::env::args().next().unwrap();
    println!(
        "Try executing this command:\nkill -{} $(pidof {})\n",
        sigrtmin + 2,
        program_name
    );

    let mut signal0 = tokio::signal::unix::signal(SignalKind::from_raw(sigrtmin))?;
    let mut signal1 = tokio::signal::unix::signal(SignalKind::from_raw(sigrtmin + 1))?;
    let mut signal2 = tokio::signal::unix::signal(SignalKind::from_raw(sigrtmin + 2))?;
    let mut signal3 = tokio::signal::unix::signal(SignalKind::from_raw(sigrtmin + 3))?;

    loop {
        tokio::select! {
            _ = signal0.recv() => {
                println!("Got signal 0.");
            }
            _ = signal1.recv() => {
                println!("Got signal 1.");
            }
            _ = signal2.recv() => {
                println!("Got signal 2.");
            }
            _ = signal3.recv() => {
                println!("Got signal 3.");
            }
            _ = interval.tick() => {
                println!("Tick.");
            }
        }
    }
}

So far, so good. But I just can't figure out how to programmatically (e.g. with a loop or a .collect()) create new signals and listen for them in the select!.

How would I go about doing that?

weisbrja
  • 164
  • 10
  • Where did you get stuck? – Chayim Friedman Mar 03 '22 at 12:24
  • Well, first I tried using `FuturesUnordered`, then `StreamMap`. But since I'm kind of new to tokio and async programming, I couldn't get it working. – weisbrja Mar 03 '22 at 12:28
  • Then please show you tries and explain why they are failures. Also, I understand your problem is with the `select!` part, not with creating the signals, am I correct? Because this is not obvious from the question. – Chayim Friedman Mar 03 '22 at 12:31
  • Is [`select_all`](https://docs.rs/futures/0.3.21/futures/future/fn.select_all.html) what you are looking for? – Jmb Mar 03 '22 at 13:15
  • `select_all` looks promising, but a `Signal` is not a `Future`, but rather a `Stream` and I don't know how to deal with that. – weisbrja Mar 04 '22 at 12:22
  • I found `futures::stream::select_all()` to be even more promising, but `Signal` doesn't implement the `futures::Stream` trait. – weisbrja Mar 04 '22 at 12:45
  • Could easily make a wrapper around `Signal` that implements `Stream` by just forwarding `poll_next`? [Edit:] Well, now that I know what to look for, that of course already [exists](https://docs.rs/tokio-stream/latest/tokio_stream/wrappers/struct.SignalStream.html). – Caesar Mar 05 '22 at 05:35

1 Answers1

1

In this kind of situation, I tend to reach for channels, because

  • I know they're cancel-safe and will resume with any unreceived element.
  • They're easy to use. Just create a sender per event you want to watch for, and use timeout on the receiving end.

That being said, they're probably not the most elegant or efficient solution. A clean solution would probably use futures::stream::select_all and tokio_stream::wrappers::SignalStream, see comments on the question. But that's a bit more difficult to understand and set up.

Anyway, the following might work:

use std::time::Duration;
use libc::{SIGUSR1, SIGUSR2};
use tokio::{
    signal::unix::SignalKind,
    sync::mpsc,
    time::{timeout_at, Instant},
};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let (send, mut recv) = mpsc::channel::<i32>(42);
    for &signum in [SIGUSR1, SIGUSR2].iter() {
        let send = send.clone();
        let mut sig = tokio::signal::unix::signal(SignalKind::from_raw(signum))?;
        tokio::spawn(async move {
            loop {
                sig.recv().await;
                if send.send(signum).await.is_err() {
                    break;
                };
            }
        });
    }

    let mut tick = Instant::now();
    loop {
        match timeout_at(tick, recv.recv()).await {
            Ok(Some(id)) => println!("Signal {id}"),
            Err(_elapsed) => {
                println!("Tick!");
                tick += Duration::from_secs(5);
            }
            Ok(None) => unreachable!(),
        }
    }
}

If you want to treat more different kinds of events, you'll have to define yourself an enum to be sent over the channel.

Caesar
  • 6,733
  • 4
  • 38
  • 44