7 releases

0.3.0 Sep 27, 2019
0.2.4 Aug 2, 2019
0.2.1 Jul 31, 2019
0.1.0 Jul 11, 2019

#35 in #managed

MIT license

21KB
188 lines

Hulunbuir

Build Status Doc

See examples folder for basic usage.


lib.rs:

Hulunbuir is a cross-thread garbage collector. The managed objects could be used in multithreads, and collecting process may happen in any of them.

Normally, reading or updating a managed object must lock global collector as well, which significantly decrease multithread performance. However, Hulunbuir does not provide common "read guard" and "write guard" interface; instead it only supports two functions: allocate and replace. The first one create a managed object, and may trigger a garbage collecting process if necessary; the second one replace the value of a managed object with a new one provided by argument. The global collector only have to be locked during replacing and the lock could be released when working thread owns the value. So the lock will not become the bottleneck of performance.

Hulunbuir also provides Slot as higher level abstraction and interface.

Basic usage

use hulunbuir::{Address, Collector, Keep};

// create a managed type
struct ListNode(i32, Option<Address>);

// implement Keep for it, so it could be managed
impl Keep for ListNode {
    fn with_keep<F: FnMut(&Address)>(&self, mut keep: F) {
        // each node keeps only its tail, so call `keep` with it...
        if let Some(tail) = &self.1 {
            // ...if the node has tail
            keep(tail)
        }
    }
}

fn main() {
    // create a collector with 128 slots available
    let mut collector = Collector::new(128);
    let root = collector.allocate(ListNode(0, None)).unwrap();
    collector.set_root(root.clone());
    let tail = collector.allocate(ListNode(1, None)).unwrap();
    // replace root node out with something not important
    let mut root_node = collector.replace(&root, ListNode(42, None)).unwrap();
    root_node.1 = Some(tail);
    // replace root node back
    let _ = collector.replace(&root, root_node).unwrap();
    
    let _orphan = collector.allocate(ListNode(2, None)).unwrap();
    // before collecting...
    assert_eq!(collector.alive_count(), 3);
    collector.collect();
    // after collecting...
    assert_eq!(collector.alive_count(), 2);
}

This replace-based object updating strategy is suitable for simple single-thread usage. The collector will work correctly only when no garbage collection happens when any "real" object is replaced out, which means, when any of them is replaced out:

  • no explicit calling to Collector::collect
  • no calling to Collector::allocate, since it may trigger collection as well if there's no slot available

In multithreading context, none of above could be archieved since each thread has no idea about what the others are doing. So more complicated strategy must be introduced. Hulunbuir provides slot module for this purpose, but you are free to develop your own one.

Dependencies

~2MB
~46K SLoC