How to catch a signal in Rust without memory leaks

Viewed 131

I noticed memory leaks in my program and tracked it down to the signal handling. Seems crazy that there isn't a leak-free way to do this. I'm not worried about the "still reachable" bytes reported by Valgrind - I'm worried about the "possibly lost" bytes.

minimal reproducible example:

use tokio::signal;
use tokio::time::{sleep, Duration};

async fn sleep1() {
    loop {
        sleep(Duration::from_secs(1)).await;
        println!("sleep1");
    }
}

async fn sleep2() {
    loop {
        sleep(Duration::from_secs(2)).await;
        println!("sleep2");
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let s1 = Box::pin(sleep1());
    let s2 = Box::pin(sleep2());
    let sig = Box::pin(signal::ctrl_c());
    tokio::select! {
        _ = s1 => {},
        _ = s2 => {},
        _ = sig => {},
    };
    println!("shutting down");
    Ok(())
}

excerpt from Cargo.toml file:

edition = "2021"
tokio = { version = "1", features = ["full"] }

valgrind output:

==1366460== Command: target/debug/simulation
==1366460== 
sleep1
sleep2
sleep1
sleep1
^Cshutting down
==1366460== 
==1366460== HEAP SUMMARY:
==1366460==     in use at exit: 25,884 bytes in 82 blocks
==1366460==   total heap usage: 617 allocs, 535 frees, 145,635 bytes allocated
==1366460== 
==1366460== LEAK SUMMARY:
==1366460==    definitely lost: 0 bytes in 0 blocks
==1366460==    indirectly lost: 0 bytes in 0 blocks
==1366460==      possibly lost: 1,188 bytes in 3 blocks
==1366460==    still reachable: 24,696 bytes in 79 blocks
==1366460==         suppressed: 0 bytes in 0 blocks
==1366460== Rerun with --leak-check=full to see details of leaked memory
==1366460== 
==1366460== For lists of detected and suppressed errors, rerun with: -s
==1366460== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
1 Answers

according to the tokio developers, this is a false positive resulting from the use of global variables. see here.

also, I found that if one really wishes to get rid of the valgrind error, it's possible to implement the signal handler in C and call it from rust. The rust program would spawn a blocking thread (something like tokio::task::spawn_blocking) that waits for the C code to catch a signal and terminate.

Related