- Removed access to
acquire_shard_guard(). Instead, users should useenqueue_guard_in_shard()for obtaining a guard for enqueuing. Dequeue does not face this consequence because it's attempt on obtaining a guard internally does not cause a cancel safety issue on data being lost; as a result, users can just directly obtain an item throughdequeue_in_shard()ordequeue() - Reduced the
ShardLockGuard<'a, T>size from 32 bytes to 24 bytes (got rid of an unnecessary enum relating to first bullet point).
- Added
CSShardedRingBuf<T>, which is a 100% cancel safe version ofShardedRingBuf<T>. Users need to acquire a guard on the shard first in an async manner before passing ownership of their item to the buffer.- Surprisingly,
CSShardedRingBuf<T>runs faster thanShardedRingBuf<T>probably due to compiler optimizations
- Surprisingly,
- Both
CSShardedRingBuf<T>andShardedRingBuf<T>cache pad theirBox<[Notify]>fields, which slightly improves performance - Updated documentation a bit
ShardedRingBuf<T>has a new instance methodnew_with_enq_numfor the purpose of using the buffer knowing how many enqueue tasks a user will spawn (useful for SPMC or if enqueuer tasks < num of shards) and distributing notifications to enqueuers evenly by dequeuers- Added Result return types on enqueue methods for
ShardedRingBuf<T> - Removed unnecessary code and async functions
- Updated documentation of
ShardedRingBuf<T>methods - Transferred over some basic tests from
ExpShardedRingBuf<T>(deprecated) toShardedRingBuf<T>(finally)
ShardedRingBuf<T>has been refactored to only support the previous Pin policy code from previous iteration (seeexp_srb/to see previous iteration)- A new hybrid lock-free/async-waiting data structure added to this crate called
MLFShardedRingBuf<T>(Most Lock Free Sharded Ring Buffer) - Benchmarking results has been collected through Criterion and shown on README.md
- Lots of refactoring and documentation updates
- Forgot to update this, so you might want to check the commit history for this
- Changed the enqueue and dequeue indices to Cell usize rather than AtomicUsize in
InnerRingBuffer<T>because everything is protected under a shard lock.
- A new policy has been made called
CFT(or Completely Fair Tasks). Seesrc/task_local_spawn.rsfor more details. - Spawn functions have been refactored to specifically call on enqueue and dequeue functions
of
ShardedRingBuf<T>(as a result, the async enqueue and dequeue functions for the buffer is private) - Updated doc comments on spawn function and Shard Policies and various other places.
- No new benchmarking details have been updated as of now due (though benchmarking code is being modularized currently).
- Crate has been deprecated and moved to
sharded_ringbufdue to high number of version changes and the crate name is a misnomer because it technically uses locks with the key feature of this buffer being that it has shards. - There have been other changes here before it was marked deprecated like
CFT(Completely Fair Tasks) being implemented, which is why the major version change has been updated here instead of patch version.
- Metadata in
LFShardedRingBuf<T>is once again refactored such that everything uses atomic primitives (i.e.Box<[UnsafeCell<MaybeUninit<T>>]>->Box<[AtomicPtr<MaybeUninit<T>>]>). It implementSendandSyncby default now! - Some methods have been renamed (for better naming conventions and these names are finalized moving forward) such as:
LFShardedRingBuf::concurrent_clear->LFShardedRingBuf::async_clearLFShardedRingBuf::get_enq_ind_for_shard->LFShardedRingBuf::get_enq_ind_at_shardLFShardedRingBuf::get_deq_ind_for_shard->LFShardedRingBuf::get_deq_ind_at_shardLFShardedRingBuf::get_item_in_shard->LFShardedRingBuf::get_item_at_shardLFShardedRingBuf::rb_items_at_shard->LFShardedRingBuf::async_clone_items_at_shardLFShardedRingBuf::rb_items->LFShardedRingBuf::async_clone_itemsLFShardedRingBuf::print_buffer->LFShardedRingBuf::async_print- Note these methods might be moved into traits in the future.
- Internally, a
ShardLockGuardtype is created and used to make locking the shard in many of these async function ergonomic and in theory cancel safe (though this of course needs testing) - All methods for this data structure have a
syncandasyncversion (though testing still needs to be performed on many of these methods for thread safety and cancel safetiness), except for the following:LFShardedRingBuf::enqueueandLFShardedRingBuf::dequeuemethods, which remains to be async. If there is interest, a sync version of these methods can be made that usesthread_localvariables, but keep in mind that this buffer highly benefits from being an asynchronous data structure as a result of context switching tasks to threads
- New benchmarking in
README.md
- Removed unnecessary metadata in
LFShardedRingBuf<T>and changedpoisonedfield insideLFShardedRingBuf<T>as well as theInnerRingBuffer<T>enqueue_ind,dequeue_ind, andjob_countfields from cell variants (i.e.Cell<usize>,Cell<bool>) to atomic variants (AtomicUsize,AtomicBool) for safe accesses among threads (and correctness) LFShardedRingBuf<T>now supports uneven shards (that is if request capacity is not divisible by requested number of shards) and balances uneven capacity among each shard nicely.- Modified how
LFShardedRingBuf::clearmethod works, which clears with Relaxed memory ordering. Added in an async clear method calledLFShardedRingBuf::concurrent_clearshould users want to clear the buffer in an asynchronous manner. - Changed
LFShardedRingBuf::poisonandLFShardedRingBuf::clear_poisonto not be async functions. Didn't make sense. Also, addedLFShardedRingBuf::is_poisonedto test if the buffer is poisoned. - Added in
LFShardedRingBuf::get_num_of_shards,LFShardedRingBuf::get_shard_capacity,LFShardedRingBuf::get_total_capacity,LFShardedRingBuf::get_job_count_at_shard, so users are exposed to reading what theLFShardedRingBuf<T>contains. Done so with Relaxed memory ordering. - Added in
rt_spawn_buffer_taskmethod in case users want to provide a specific Tokio Runtime to spawn a task rather than work with[tokio::main]macro. - Renamed
LFShardedRingBuf::next_enqueue_index_for_shardandLFShardedRingBuf::next_dequeue_index_for_shardtoLFShardedRingBuf::get_enq_ind_for_shardandLFShardedRingBuf::get_deq_ind_for_shardrespectively for better naming conventions. - README updated with more tips on how to use this buffer and new benchmarking is shown.
- Removed
LFShardedRingBuf::poison_deqmethod. UseLFShardedRingBuf::poisonmethod instead (see examples fromREADME.md). - Improved performance of
LFShardedRingBuf<T>through usingMaybeUninit<T>(instead ofOption<T>) for eachInnerRingBuffer<T>items- As a result,
Droptrait has been implemented forInnerRingBuffer<T>
- As a result,
- Updated cargo benchmarking details in
README.mdand added inbenchmark_res/folder to store old benchmark data.
- Added Apache licensing in repository
- Added
CHANGELOG.md(will update this frequently from now on) - Added
CONTRIBUTING.md
- Removed unnecessary dependencies from
Cargo.toml - Repository code is modularized for organization and readability
- LFShardedRingBuf fields have been modified to use only AtomicBool for shard locking and the job_count is kept inside InnerRingBuffer. This should promote cache locality on accessing and updating job_count when enqueuing/dequeuing.
- When a shard is acquired within
try_acquire_shard(), if a dequeuer thread noticed that the shard is empty or an enqueuer thread noticed that the shard is full afterward, it will release that shard with a Relaxed memory ordering. Previously, this was done in a Release memory ordering, which was not necessary as this release was not modifying the respective InnerRingBuffer. Doing this possibly saved a couple of cycles. - Testing for SPSC/SPMC/MPSC/MPMC is implemented in tests directory.
is_empty(),is_full(),clear(), andpoison()have also been tested. There are other functions that must be tested here and stress/fuzz testing should be done here, but it should be safe to use this structure in an async environment to enqueue and dequeue items. - Cargo benchmarking added for single threaded and multithreaded cases
- Implemented LFShardedRingBuf, which features:
- It uses multiple smaller simple ring buffers (shards) each with capacity = requested capacity / # of shards. This value is ceiled up currently so all shard have the same capacity.
- It is lock-free; only uses atomic primitives and no mutexes or rwlocks
- False sharing is avoided through cache padding the shard atomic locks + shards.
- It uses tokio's task local variables as a shard index reference and to remember the shard acquistion policy strategy to take for tasks to effectively acquire a shard to enqueue/dequeue on.
Exponential backoff + random jitter (capped at 20 ms) used to yield CPU in functions that loops.- This backoff method was removed since it introduced a bit more delay. This is because what you want to yield here are not the threads but rather the asynchronous tasks; you want the threads to always be working and let tokio reassign these threads to a task that will actually perform work. Instead of sleeping the threads, if a thread performs a full sweep around the shards (a sweep defined in respect to the shard acquisition policies), tokio's
yield_now()function will put the running task to the back of the scheduled list. Doing this in a way also enables that task to actually perform work as an enqueuer or dequeuer later on because presumably a successful dequeue or enqueue operation has occurred by then. - Also, unfortunately,
tokio::time::sleep()function works on a millisecond granularity, so sleeping on a task could be a bit more costly.
- This backoff method was removed since it introduced a bit more delay. This is because what you want to yield here are not the threads but rather the asynchronous tasks; you want the threads to always be working and let tokio reassign these threads to a task that will actually perform work. Instead of sleeping the threads, if a thread performs a full sweep around the shards (a sweep defined in respect to the shard acquisition policies), tokio's
- Different shard acquisition policies are provided:
Sweep,RandomAndSweep, andShiftBy(seesrc/shard_policies.rsfor more info) - It can perform in an async multithreaded or async single threaded environment (optimal for multiple producer, multiple consumer situations)
- Added MIT Licensing