Memory leak while working with Arc/threads

Hello there.
I have recently been working on a Rust projects that features multithreading.
The application is structured like this:

  • A CLI thread (handles stdin etc.)
  • The main thread listening for connections (server)
  • Each connection is then processed in a different thread.

I have a stop command that shuts the server down, therefore I need to pass a reference to the TcpListener in order to drop it.
Since I need multiple ownership (the listener is also used for the connections), I use Arc<TcpListener>, then extract a reference using as_ref() in the new thread.

Everything works fine, however when I analyze the executable (in release mode) with Valgrind, I get warned that 304 bytes could have been possibly lost.

I can't manage to find the cause to this nor fix it.

Relevant files:
https://github.com/RoccoDev/bbCraft/blob/master/server/mc_server_impl/src/net/mod.rs (Network stuff)
https://github.com/RoccoDev/bbCraft/blob/master/server/mc_server_impl/src/cli/mod.rs (CLI)

Thank you for any help you're willing to give.

/// Attempts to stop the server.
pub fn stop(listener: &TcpListener) {
    println!("Stopping server...");
    unsafe { crate::api::server_unload(); }
    
    println!("Shutting down TCP listener...");
    drop(listener); // this does nothing

    print!("Exited succesfully.");
    std::process::exit(0);
}

Your listener is a shared reference (which is therefore Copy), so drop-ping it does literally nothing. I advise you to use clippy (rustup component add clippy once, followed by cargo clippy instead of cargo check), since it has many lints against this kind of code smells


Now, regarding the memory leak, in

pub fn accept_user_input(listener: &TcpListener) {
    loop {
        print!("> ");
        // stdout only flushes on newlines,
        // and the above 'print' macro does not include one.
        io::stdout().flush();

        let mut buf = String::new();
        io::stdin().read_line(&mut buf);

        match buf.trim() {
            "stop" => {
                crate::net::stop(listener); // calls process::exit()
            },
            (input) => {
                println!("{}: command not found.", input);
            }
        }

    }
}

one thing that I am pretty sure is never freed is buf, since the program dies before it goes out of scope.
You can add

"stop" => {
    buf = String::new(); // overrides allocated buffer with an empty string, effectively freeing the buffer
    crate::net::stop(listener); // calls process::exit()
},

Finally, calling exit() is an abrupt way to end your program, I think it will generally be a tough challenge to avoid a "valgrind leak" in such cases :sweat_smile:

Thank you for the reply and the awesome explanation.
I had obviously missed that process::exit does not call destructor etc.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.