Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Calling fast_clear causes a memory leak when using Rc<T>, even if force_unlink is called on each linked object. #81

Open
icmccorm opened this issue Feb 9, 2023 · 2 comments

Comments

@icmccorm
Copy link
Contributor

icmccorm commented Feb 9, 2023

Miri found memory leaks in the following tests:

  • xor_linked_list::tests::test_force_unlink
  • linked_list::tests::test_force_unlink
  • singly_linked_list::tests::test_fast_clear
  • rbtree::tests::test_fast_clear

All assertions pass. Each test has a similar structure; here's a minimal example using LinkedList that recreates the issue:

let mut l = LinkedList::new(ObjAdapter1::new());
      
let a = make_obj(1);

l.cursor_mut().insert_before(a.clone());

l.fast_clear();

unsafe {
    a.link1.force_unlink();
}

When executing this, Miri detects that a is leaked and provides the following output:

The following memory was leaked: alloc94446 (Rust heap, size: 56, align: 8) {
    0x00 │ 01 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 │ ................
    0x10 │ 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │ ................
    0x20 │ 01 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 │ ................
    0x30 │ 01 00 00 00 __ __ __ __                         │ ....░░░░
}

I originally thought this was due to unwrapping the clone in insert_before(...). To test this, I switched to using a modified ObjAdapter3 with Weak instead of Rc, and passed Rc::downgrade(&a) as a parameter to insert_before instead of making a clone. However, this did not correct the issue.

Given the descriptions of both force_unlink and fast_clear, I'm guessing that there are always going to be some precautions necessary to avoid memory leaks here. Could you confirm if this is a bug, and if not, what additional steps a user would need to take to avoid this type of leak when using these methods? Thanks!

@icmccorm icmccorm changed the title Calling fast_clear causes a memory leak when using Rc, even if force_unlinnk is called on each object Calling fast_clear causes a memory leak when using Rc, even if force_unlink is called on each object Feb 9, 2023
@icmccorm icmccorm changed the title Calling fast_clear causes a memory leak when using Rc, even if force_unlink is called on each object Calling fast_clear causes a memory leak when using Rc<T>, even if force_unlink is called on each linked object. Feb 9, 2023
@Amanieu
Copy link
Owner

Amanieu commented Feb 9, 2023

I think this is not so much a bug as poor API design. force_unlink was originally designed to be used with UnsafeRef where the lifetimes of objects are manually managed: you could fast_clear a list and then re-use the existing objects in another list by force_unlinking them.

This was never designed to work with proper pointer types such as Box or Rc.

@icmccorm
Copy link
Contributor Author

icmccorm commented Feb 9, 2023

Gotcha! To clarify my thought process here:

I'm new to working with Rc, so I had thought that you could get around this with a change to using Weak, but from the docs:

Since a Weak reference does not count towards ownership, it will not prevent the value stored in the allocation from being dropped... Note however that a Weak reference does prevent the allocation itself (the backing store) from being deallocated.

And, sure enough, the same kind of leak will happen with a pretty minimal example:

use std::{rc::{Rc, Weak}};
fn main() {
    let a = Rc::new(Box::new(1));
    Weak::into_raw(Rc::downgrade(&a));
}

The pull request above eliminates the leak by switching away from Rc to UnsafeRef for the these test cases.

@icmccorm icmccorm mentioned this issue Feb 23, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants