GithubHelp home page GithubHelp logo

claytonwramsey / dumpster Goto Github PK

View Code? Open in Web Editor NEW
142.0 1.0 4.0 260 KB

Cycle-tracking garbage collector library for Rust

License: GNU General Public License v3.0

Rust 98.55% Python 1.45%
garbage-collection rust

dumpster's Introduction

dumpster: A cycle-tracking garbage collector for Rust

dumpster is an cycle-detecting garbage collector for Rust. It detects unreachable allocations and automatically frees them.

Why should you use this crate?

In short, dumpster offers a great mix of usability, performance, and flexibility.

  • dumpster's API is a drop-in replacement for std's reference-counted shared allocations (Rc and Arc).
  • It's very performant and has builtin implementations of both thread-local and concurrent garbage collection.
  • There are no restrictions on the reference structure within a garbage-collected allocation (references may point in any way you like).
  • It's trivial to make a custom type collectable using the provided derive macros.
  • You can even store ?Sized data in a garbage-collected pointer!

How it works

dumpster is unlike most tracing garbage collectors. Other GCs keep track of a set of roots, which can then be used to perform a sweep and find out which allocations are reachable and which are not. Instead, dumpster extends reference-counted garbage collection (such as std::rc::Rc) with a cycle-detection algorithm, enabling it to effectively clean up self-referential data structures.

For a deeper dive, check out this blog post.

What this library contains

dumpster actually contains two garbage collector implementations: one thread-local, non-Send garbage collector in the module unsync, and one thread-safe garbage collector in the module sync. These garbage collectors can be safely mixed and matched.

This library also comes with a derive macro for creating custom collectable types.

Examples

use dumpster::{Collectable, unsync::Gc};

#[derive(Collectable)]
struct Foo {
    ptr: RefCell<Option<Gc<Foo>>>,
}

// Create a new garbage-collected Foo.
let foo = Gc::new(Foo {
    ptr: RefCell::new(None),
});

// Insert a circular reference inside of the foo.
*foo.ptr.borrow_mut() = Some(foo.clone());

// Render the foo inaccessible.
// This may trigger a collection, but it's not guaranteed.
// If we had used `Rc` instead of `Gc`, this would have caused a memory leak.
drop(foo);

// Trigger a collection. 
// This isn't necessary, but it guarantees that `foo` will be collected immediately (instead of 
// later).
dumpster::unsync::collect();

Installation

To install, simply add dumpster as a dependency to your project.

[dependencies]
dumpster = "0.1.2"

Optional features

dumpster has two optional features: derive and coerce-unsized.

derive is enabled by default. It enables the derive macro for Collectable, which makes it easy for users to implement their own collectable types.

use dumpster::{unsync::Gc, Collectable};
use std::cell::RefCell;

#[derive(Collectable)] // no manual implementation required
struct Foo(RefCell<Option<Gc<Foo>>>);

let my_foo = Gc::new(Foo(RefCell::new(None)));
*my_foo.0.borrow_mut() = Some(my_foo.clone());

drop(my_foo); // my_foo will be automatically cleaned up

coerce-unsized is disabled by default. This enables the implementation of CoerceUnsized for each garbage collector, making it possible to use Gc with !Sized types conveniently.

use dumpster::unsync::Gc;

// this only works with "coerce-unsized" enabled while compiling on nightly Rust
let gc1: Gc<[u8]> = Gc::new([1, 2, 3]);

To use coerce-unsized, edit your installation to Cargo.toml to include the feature.

[dependencies]
dumpster = { version = "0.1.2", features = ["coerce-unsized"]}

License

This code is licensed under the GNU GPLv3 any later version of the GPL at your choice. For more information, refer to LICENSE.md.

dumpster's People

Contributors

anna-hope avatar claytonwramsey avatar mwpuppire avatar ray33ee avatar tshepang avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

dumpster's Issues

Cycles, Gc as Deref during Drop

I read your blog post. Cool stuff.

I was curious, so I tried the program below. I didn't expect it to work :-). and indeed it panics, saying "thread 'main' panicked at 'dereferencing GC to already-collected object',".

I think that it would be useful to document that dereferencing a Gc for a GC'd object can crash. AFAICT this can only occur if a Gc contained within a T is accessed in T's drop impl. Am I right? (A determined programmer could of course, having gained access to a "bad" Gc in a drop impl, break things elsewhere by feeding them the bad Gc.)

Also, maybe some people doing funky stuff during drop would want a try deref method. I suggest impl Gc<T> { fn try_deref(this: &Self) -> Option<&T>

use std::cell::RefCell;

use dumpster::{Collectable, unsync::Gc};

#[derive(Collectable, Debug)]
struct Loopy {
    value: u32,
    loopy: RefCell<Option<Gc<Loopy>>>,
}

impl Drop for Loopy {
    fn drop(&mut self) {
        eprintln!(
            "drop {:?} {:?}",
            self.value,
            self.loopy.borrow().as_ref().map(|gcl| gcl.value),
        );
    }
}

fn main() {
    let a = Gc::new(Loopy {
        value: 42,
        loopy: None.into(),
    });
    let b = Gc::new(Loopy {
        value: 66,
        loopy: None.into(),
    });
    *a.loopy.borrow_mut() = Some(b.clone());
    *b.loopy.borrow_mut() = Some(a.clone());
    drop(a);
    drop(b);
    eprintln!("returning from main");
}

Just props for the crate, nothing else :)

This crate is awesome.

I wish this was created a few years ago when I created Wasmer and we were trying to optimize Wasm initialization. It would have come very handy!
Keep up the good work :)

Unsoundness when dropping cycles with custom `Drop` implementation

In this code...

#[test]
fn use_after_drop() {
    #[derive(Collectable)]
    struct Demo {
        // for debug printing
        id: i32,
        // number of times `drop` was called on this struct
        dropped: i32,
        // for making cycles
        other: RefCell<Option<Gc<Demo>>>
    }
    impl Drop for Demo {
        fn drop(&mut self) {
            if let Some(other) = self.other.borrow().as_ref() {
                if other.dropped > 0 {
                    // if `other` had been dropped, we shouldn't be able to access it
                    panic!("accessing gc object with id {} after it was dropped!!", other.id);
                }
            }
            self.dropped += 1;
        }
    }

    // make a cycle

    let gc1 = Gc::new(Demo {
        id: 1,
        dropped: 0,
        other: Default::default()
    });
    let gc2 = Gc::new(Demo {
        id: 2,
        dropped: 0,
        other: RefCell::new(Some(gc1.clone()))
    });
    *gc1.other.borrow_mut() = Some(gc2.clone());
}

the drop call for the second Demo object is able to access the first Demo object after it was dropped.

Suggested fix: make #[derive(Collectible)] generate a Drop impl so the user can't supply one. This is not a big functionality hit because people can always write something like this

let my_gced_object = MyStruct {
    gc1: Gc::new(...),
    gc2: Gc::new(...),
    actual_data: MyStructInner {
        ...
    }
}
impl Drop for MyStructInner {
    ...
}

Map collections

I got an error about BTreeMap not implementing Collectable and I don't see an impl for HashMap either if I read the code right.

Consider switching to LGPL

I might consider moving dumpster over to a LGPL license for this collector, especially if there are people interested in it who are constrained by the license. However, I won't do so unless there's any people who show some desire for this to change. Feel free to reply with feedback if you have license opinions.

Implement Collectable for UnsafeCell

The three other main cell types have implementations, and UnsafeCell can be implemented with:

unsafe impl<T: Collectable + ?Sized> Collectable for std::cell::UnsafeCell<T> {
    #[inline]
    fn accept<V: Visitor>(&self, visitor: &mut V) -> Result<(), ()> {
        unsafe { (& mut *self.get()).accept(visitor) }
    }
}

I cast to a & mut here as it has Visitor implemented, but maybe & could be used instead?

Add Gc::as_ptr by analogy with Rc::as_ptr

The standard library Rc has a method as_ptr to get the raw pointer to the value wrapped by the Rc, which I rely on in several places. Would you be interested in a PR that added that functionality to this crate?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.