Is there a REAL memory leak?

Hi guys, I ran into a Possible Memory Leak issue recently. The issue is about how a static variable frees its memory. The complete code I used is shown as below:

use lazy_static::lazy_static;
use std::collections::HashMap;
use std::sync::RwLock;


#[derive(Clone, Debug)]
struct Circle {
    radius: i32,
}
impl Drop for Circle {
    fn drop(&mut self) {
        println!("dropping circle: {}", self.radius);
    }
}


// define a static variable
lazy_static! {
    static ref HASHMAP: RwLock<HashMap<String, Circle>> = RwLock::new(HashMap::new());
}


fn main() {
    {
        let mut m = HASHMAP.write().unwrap();
        (*m).insert("c1".to_string(), Circle { radius: 10 });
    }

    {
        // drop `c1`
        let mut m = HASHMAP.write().unwrap();
        let c1 = m.remove("c1").unwrap();
        drop(c1);
    }

    let size = HASHMAP.read().unwrap().len();
    println!("size: {}", size);
}

I compiled the code above and used Valgrind to perform memory check:

# compile
cargo build --release

# run memory check
valgrind --leak-check=full --show-leak-kinds=definite,possible --log-file="result" target/release/test_static_map

The complete Valgrind report is

==688705== Memcheck, a memory error detector
==688705== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==688705== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==688705== Command: target/release/test_static_map
==688705== Parent PID: 688558
==688705== 
==688705== 
==688705== HEAP SUMMARY:
==688705==     in use at exit: 148 bytes in 1 blocks
==688705==   total heap usage: 13 allocs, 12 frees, 3,331 bytes allocated
==688705== 
==688705== 148 bytes in 1 blocks are possibly lost in loss record 1 of 1
==688705==    at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==688705==    by 0x10E13E: _ZN9hashbrown3raw21RawTable$LT$T$C$A$GT$14reserve_rehash17h0b184eda0aaee77dE.llvm.9777120827388291557 (in /home/ubuntu/workspace/test_static_map/target/release/test_static_map)
==688705==    by 0x112573: hashbrown::map::HashMap<K,V,S,A>::insert (in /home/ubuntu/workspace/test_static_map/target/release/test_static_map)
==688705==    by 0x111E8A: test_static_map::main (in /home/ubuntu/workspace/test_static_map/target/release/test_static_map)
==688705==    by 0x112432: std::sys_common::backtrace::__rust_begin_short_backtrace (in /home/ubuntu/workspace/test_static_map/target/release/test_static_map)
==688705==    by 0x112448: _ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17h70a0ed67c352bcbbE.llvm.8328676566673015485 (in /home/ubuntu/workspace/test_static_map/target/release/test_static_map)
==688705==    by 0x129C1A: std::rt::lang_start_internal (function.rs:284)
==688705==    by 0x112424: main (in /home/ubuntu/workspace/test_static_map/target/release/test_static_map)
==688705== 
==688705== LEAK SUMMARY:
==688705==    definitely lost: 0 bytes in 0 blocks
==688705==    indirectly lost: 0 bytes in 0 blocks
==688705==      possibly lost: 148 bytes in 1 blocks
==688705==    still reachable: 0 bytes in 0 blocks
==688705==         suppressed: 0 bytes in 0 blocks
==688705== 
==688705== For lists of detected and suppressed errors, rerun with: -s
==688705== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

My question is about these two parts:

  • "148 bytes in 1 blocks are possibly lost in loss record 1 of 1"
  • "ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)"

After lots of experiments, for now, it seems the "possibly lost" could be caused by the static variable. I'm a newbie of Valgrind, so I am not sure if Valgrind could detect the memory free of a lazy_static variable. Do you have any idea about the "possibly lost"? Any comment is welcome. Thanks a lot!

1 Like

It does not.

There is no free to detect. Valgrind is correct that whatever heap memory is associated with this variable is never freed. (Assuming these messages are really about the static and not about something else; it can be hard to tell.) There's no point in time where it's "safe" to drop statics (if you try, you start getting into problems where you have to ask in what order different ones get dropped, and it's a whole can of worms), so Rust doesn't try.

Whether that's a real memory leak or not can be kind of subjective. If you are using the memory throughout the program's runtime and there's no particular point before the end of the program when it would make sense to delete it, is that really a leak? Would it make sense to add code to the end of main to clean it up right before the OS comes through and sweeps it all away anyway? It's kind of a philosophical question.

4 Likes