3

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
    This almost feels like a bug report, so have you tried raising this with the developers who maintain Rust? If you join their Zulip community you'll probably get some attention asking about this. – Bobulous Jun 07 '22 at 20:05
  • @Bobulous - that's a good idea. i haven't communicated with the developers yet. i'll do that. – user7275233 Jun 07 '22 at 20:26
  • 1
    If the issue is with the signal handling, why would you bring it up to the Rust devs instead of the Tokio devs? If it is a Rust issue as seen through a Tokio API, then it's best for *them* to see it resolved. Regardless, I wouldn't be surprised if initializing a signal handler uses some global state (which wouldn't be cleaned up at exit). – kmdreko Jun 07 '22 at 21:58
  • 1
    I'm surprised, because I've run almost this very code through LLVM's ASAN for almost a year now and never got a leak report. I know that valgrind is not 100% compatible with tokio, so it might be a false positive of valgrind. – Finomnis Jun 08 '22 at 00:51
  • https://github.com/tokio-rs/tokio/issues or https://discord.com/invite/tokio – Finomnis Jun 08 '22 at 00:54
  • 1
    I can confirm that the code you have does not contain a memory leak according to LLVM ASAN. It was able to detect `Box::leak()` properly when I added it. – Finomnis Jun 08 '22 at 01:00
  • This might be related: https://github.com/tokio-rs/tokio/issues/1092 – Finomnis Jun 08 '22 at 01:02
  • 1
    @kmdreko that's what I did. I raised the issue [here](https://github.com/tokio-rs/tokio/issues/4756). I also devised a work-around. I wrote a C library to wait until a signal is called and I made a wrapper to call the library from Rust. it got rid of the "possible leaks". – user7275233 Jun 08 '22 at 03:04
  • If it's implementable in C, it's implementable in Rust. (unsafe Rust is a superset of C, as far as I know). The question is if one can cleanly integrate it in the Tokio library API without storing lazy statics. (which are the real reason for the warning, from my understanding) – Finomnis Jun 08 '22 at 09:15

1 Answers1

0

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.