flaque 0.1.0

A fast, concurrent, lock-free, and sometimes wait-free, pool implementation
Documentation
  • Coverage
  • 100%
    15 out of 15 items documented7 out of 13 items with examples
  • Size
  • Source code size: 86.8 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 365.7 kB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 1s Average build duration of successful builds.
  • all releases: 1s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • Repository
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • Hywan

flaque

A fast, concurrent, lock-free, and sometimes wait-free, pool implementation.

Quick overview

A pool is a collection of objects of limited size. When an object is fetched from the pool, the number of available objects decreases by one. Once the fetched object is dropped, it goes back into the pool, and the number of available objects is increased by one.

All objects are lazily created with a builder. Objects are recycled, i.e. when an object has been built and fetched once, it can be mutated, and when it goes back in the pool, it stays as is, untouched.

use flaque::Pool;

// Let's define what the pool will hold.
struct T {
    x: u8,
}

impl Default for T {
    fn default() -> Self {
        Self { x: 0 }
    }
}

// Let's create the pool: capacity of 8, and the builder for the `T`s.
let pool = Pool::new(8, T::default);

// Let's try to get an object from the pool.
let object_0 = pool.try_get().unwrap();

// Or, let's wait to get an object from the pool.
let object_1 = pool.get().await;

The main methods are:

  • Pool::try_get if one wants to synchronously, atomically get an object from the pool,
  • Pool::get if one wants to asynchronously get an object from the pool.

An object is represented by the Object<T> type, which derefs and deref_muts to T. When it drops, the value returns immediately to the pool.

Features

Lock- and wait-free

This pool implementation is lock-free and sometimes wait-free. It is designed to be fast and consistent.

Operation Lock-free Wait-free
Pool::try_get yes yes
Pool::get yes no
Object::drop yes yes
Pool::close yes no
Pool::reopen yes yes

If you're not familiar with these definitions, please read Non-blocking algorithm.

Get and Close futures

Pool::get returns a Get future. It resolves to Some(object) once one is available in the pool, otherwise it returns to None if the pool is closed.

Pool::close returns a Close future. It resolves once all Objects are back into the pool, i.e. when they are all dropped. Pool::close takes self, i.e. it takes ownership of the pool. It will be returned back once the future resolves. This guarantees that it is impossible to reopen the pool, and to fetch new Objects, while waiting for all Objects to return back into the pool. Thus, this is not unusual to see:

let pool = pool.close().await;

Testing

This crate is tested with loom and miri.

Benchmarks

This crate has started as a hobby project, but also with the idea of replacing deadpool inside the Matrix Rust SDK. Indeed, we are experiencing performance issues due to deadpool. Here are the results of the benchmarks comparing flaque with deadpool.

Each benchmark basically runs the following code: pool.get().await; —i.e. get and release many objects— multiple times, on different threads,

The first group (pool size of 16, and 8 readers) represents the no contention scenario: there are ½ readers per object. The second group (pool size of 16, 2048 readers) represents the high contention on a small pool scenario: there are 128 readers per object. The third group (pool size of 512, 1 million readers) represents the high contention on a large pool scenario: there are 2048 readers per object.

On average, flaque is 1.7 times faster than deadpool.

The time is in milliseconds. The throughput is in millions of objects per second.