What is a minimal example of an Rc dependency cycl

2020-04-08 14:28发布

问题:

I'm trying to write a Rust program that leaks memory due to cycles with reference counts. The following example, which seems like it should cause a memory leak, does not leak memory according to Valgrind. What gives?

test.rs:

use std::cell::RefCell;
use std::rc::Rc;

struct Foo {
    f: Rc<Bar>,
}

struct Bar {
    b: RefCell<Option<Rc<Foo>>>,
}

fn main() {
    let bar = Rc::new(Bar {
        b: RefCell::new(None),
    });
    let foo = Rc::new(Foo { f: bar.clone() });
    *bar.b.borrow_mut() = Some(foo.clone());
}

Valgrind output:

$ rustc --version
rustc 1.4.0 (8ab8581f6 2015-10-27)
$ rustc -o test test.rs
$ valgrind test
==23331== Memcheck, a memory error detector
==23331== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==23331== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
==23331== Command: test
==23331== 
==23331== 
==23331== HEAP SUMMARY:
==23331==     in use at exit: 0 bytes in 0 blocks
==23331==   total heap usage: 37 allocs, 37 frees, 9,078 bytes allocated
==23331== 
==23331== All heap blocks were freed -- no leaks are possible
==23331== 
==23331== For counts of detected and suppressed errors, rerun with: -v
==23331== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

回答1:

With the version of Rust you were using, it's most likely that you were using jemalloc, which doesn't always play well with Valgrind (see the linked questions for more information). With modern versions of Rust, the system allocator is used by default and the code you have posted does report memory leaks:

$ valgrind --leak-check=full ./test
==761== Memcheck, a memory error detector
==761== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==761== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==761== Command: ./test
==761==
==761==
==761== HEAP SUMMARY:
==761==     in use at exit: 56 bytes in 2 blocks
==761==   total heap usage: 13 allocs, 11 frees, 2,233 bytes allocated
==761==
==761== 56 (32 direct, 24 indirect) bytes in 1 blocks are definitely lost in loss record 2 of 2
==761==    at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==761==    by 0x10BDAB: alloc::alloc::alloc (in /tmp/test)
==761==    by 0x10BD17: alloc::alloc::exchange_malloc (in /tmp/test)
==761==    by 0x10C3F6: <alloc::rc::Rc<T>>::new (in /tmp/test)
==761==    by 0x10BF6F: test::main (in /tmp/test)
==761==    by 0x10DAF2: std::rt::lang_start::{{closure}} (in /tmp/test)
==761==    by 0x115CC2: {{closure}} (rt.rs:49)
==761==    by 0x115CC2: std::panicking::try::do_call (panicking.rs:297)
==761==    by 0x117BA9: __rust_maybe_catch_panic (lib.rs:87)
==761==    by 0x11677C: try<i32,closure> (panicking.rs:276)
==761==    by 0x11677C: catch_unwind<closure,i32> (panic.rs:388)
==761==    by 0x11677C: std::rt::lang_start_internal (rt.rs:48)
==761==    by 0x10DAD4: std::rt::lang_start (in /tmp/test)
==761==    by 0x10C19A: main (in /tmp/test)
==761==
==761== LEAK SUMMARY:
==761==    definitely lost: 32 bytes in 1 blocks
==761==    indirectly lost: 24 bytes in 1 blocks
==761==      possibly lost: 0 bytes in 0 blocks
==761==    still reachable: 0 bytes in 0 blocks
==761==         suppressed: 0 bytes in 0 blocks
==761==
==761== For counts of detected and suppressed errors, rerun with: -v
==761== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

I am using Valgrind 3.13.0 with Rust 1.34.1 on Ubuntu, but I do not believe that would change the results.

You can also add dummy values to your structs to more easily find them in the output. I used a Box<[u8; 10240]> which stands out pretty well.

As for minimal, I'd model a linked list:

use std::cell::RefCell;
use std::rc::Rc;

struct Node {
    next: RefCell<Option<Rc<Node>>>,
}

fn main() {
    let foo1 = Rc::new(Node {
        next: RefCell::new(None),
    });
    let foo2 = Rc::new(Node {
        next: RefCell::new(Some(foo1.clone())),
    });
    *foo1.next.borrow_mut() = Some(foo2.clone());
}

This program also reports leaks.

See also:

  • Why does Valgrind not detect a memory leak in a Rust program using nightly 1.29.0?
  • Valgrind shows no allocations