Expand description

Corundum is a crate with an idiomatic persistent memory programming interface and leverages Rust’s type system to statically avoid most common persistent memory programming bugs. Corundum lets programmers develop persistent data structures using familiar Rust constructs and have confidence that they will be free of those bugs.

Statically Prevented Bugs

Common BugsExplanation Approach
Inter-Pool PointersA pointer in another pool which is unavailableType checking pools in persistent pointers.
P-to-V PointersA persistent pointer pointing at volatile memoryPersistent pointers accept only PSafe types and volatile pointers are !PSafe. Only, VCell allows single-execution P-to-V pointers.
V-to-P PointersA volatile pointer keeping a zero-referenced object aliveOnly VWeak allows V-to-P pointers which is a weak reference and does not keep data alive.
Unlogged UpdatesAn unrecoverable update to persistent dataModifications are enforced to be inside atomic transactions.
Data RaceUpdating persistent data simultaneously in two threadsMutable borrowing is limited to PMutex which uses a transaction-wide lock to provide both atomicity and isolation.
Locked MutexA persistent mutex remains locked on powerfailPMutex uses VCell which resets at restart.
Memory Leaks*An allocated memory becomes unreachablePersistent objects, except the root object, cannot cross transaction boundaries, and memory allocation is available only inside a transaction. Therefore, the allocation can survive only if there is a reference from the root object (or a decedent of it) to the data.
* Cyclic references are not prevented in this version, which lead to a memory leak. Please visit this link for the information on how to manually resolve that issue.

For more technical details on the implementation, please refer to Corundum’s academic paper and/or watch the presentation 📺.

Persistent Objects

Persistent objects in Corundum are available through persistent pointers:

  • Pbox: A pointer type for persistent memory allocation.
  • Prc: A single-threaded reference-counting persistent pointer.
  • Parc: A thread-safe reference-counting persistent pointer.

Programming Model

Persistent memory is available as a file on a DAX-enable file system such as EXT4-DAX or NOVA. These files are called memory pools. Corundum allows memory pool types rather than memory pool objects to enforce pointer safety while compilation. The trait MemPool provides the necessary functionalities for the pool type.

The first step is to open a memory pool file in the program to be able to work with persistent data. The default module provides a default memory pool type (Allocator). To open a pool, we can invoke open<T>() function which [initializes and] returns a reference to the root object of type T.

Data modification is provided and allowed only through transactional interface. None of the persistent pointers is mutably dereferencing for safety. Mutable objects are allowed via interior mutability of any of the following memory cells:

  • PCell<T,P> (or PCell<T>): An unborrowable, mutable persistent memory location for a value of type T in pool P.
  • PRefCell<T,P> (or PRefCell<T>): A mutable persistent memory location with dynamically checked borrow rules for a value of type T in pool P.
  • PMutex<T,P> (or PMutex<T>): A mutual exclusion primitive useful for protecting shared persistent data of type T in pool P.

The following example creates a pool file for a linked-list-based stack, and obtains the root object of type Node.

use corundum::default::*;
 
// Aliasing the pool type for convenience
type P = Allocator;
 
#[derive(Root)]
struct Node {
    value: i32,
    next: PRefCell<Option<Prc<Node>>>
}
 
fn main() {
    let head = P::open::<Node>("foo.pool", O_CF).unwrap();
 
    P::transaction(|j| {
        let mut h = head.next.borrow_mut(j);
        *h = Some(Prc::new(Node {
            value: rand::random(),
            next: head.next.pclone(j)
        }, j));
    }).expect("Unsuccessful transaction");
}

Re-exports

pub use stm::transaction;
pub use prc::Prc;
pub use vec::Vec as PVec;

Modules

The default allocator module

Low-level utils

Open pool flags

Single-threaded reference-counting persistent pointers

Manually manage memory through raw pointers

A Result type with string error messages

Software transactional memory APIs

Useful synchronization primitives

A contiguous growable array type with heap-allocated contents, written Vec

Macros

This macro creates a new pool module and aliases for persistent types. It generates type Allocator which a persistent allocator type. It is recommended to alias the Allocator type for tidiness.

This macro can be used to access static data of an arbitrary allocator

Structs

A simple wrapper around a type to assert that it is safe to go in a transaction.

Buddy Allocation Algorithm

A Journal object to be used for writing logs onto

A memory cell which is initialized on the first access

A persistent mutable memory location with recoverability

A transaction-wide recursive mutual exclusion primitive useful for protecting shared data while transaction is open. Further locking in the same thread is non-blocking. Any access to data is serialized. Borrow rules are checked dynamically to prevent multiple mutable dereferencing.

A persistent memory location with safe interior mutability and dynamic borrow checking

A UTF-8 encoded, growable string.

A thread-safe reference-counting persistent pointer. ‘Parc’ stands for ‘Persistent Atomically Reference Counted’.

A pointer type for persistent heap allocation.

Root object container

A persistent memory location containing a volatile data valid during a single transaction

A persistent memory location containing a volatile data

Memory Zones

Constants

Default pool memory size to be used while creating a new pool

Shows that the pool has a root object

Traits

The implementing type can be asserted TxInSafe albeit being !TxInSafe by using AssertTxInSafe.

Persistent Memory Pool

A common trait for the ability to explicitly duplicate an object.

An equivalent to From for persistent memory which requires a Journal to operate

It marks the implementing type to be free of pointers to the volatile heap, and persistence safe.

Safe to be sent to another thread

Determines how much of the MemPool is used for the trait object.

Creates a default value of the type

It is equal to UnwindSafe, but is used to ensure doubly that mutable references cannot go inside a transaction.

It marks the implementing type to be safe crossing transaction boundaries

Safe to be stored in volatile memory useful in VCell type to prevent storing persistent pointers in VCell

Functions

Derive Macros