Skip to content

Commit

Permalink
Fix Heap construction from Vec of ZST, enforce exact capacity for Hea…
Browse files Browse the repository at this point in the history
…p::new, bump version
  • Loading branch information
agerasev committed Sep 25, 2024
1 parent c947327 commit 9cf82af
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 24 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ readme = "README.md"
license = "MIT/Apache-2.0"

[workspace.dependencies]
ringbuf = { path = ".", version = "0.4.4" }
ringbuf = { path = ".", version = "0.4.5" }

[workspace]
members = ["async", "blocking"]

[package]
name = "ringbuf"
version = "0.4.4"
version = "0.4.5"
edition.workspace = true
authors.workspace = true
description = "Lock-free SPSC FIFO ring buffer with direct access to inner data"
Expand Down
14 changes: 7 additions & 7 deletions src/rb/local.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ use core::{
ptr,
};

struct End {
struct Endpoint {
index: Cell<usize>,
held: Cell<bool>,
}

impl End {
fn new(index: usize) -> Self {
impl Endpoint {
const fn new(index: usize) -> Self {
Self {
index: Cell::new(index),
held: Cell::new(false),
Expand All @@ -37,8 +37,8 @@ impl End {
///
/// Slightly faster than multi-threaded version because it doesn't synchronize cache.
pub struct LocalRb<S: Storage + ?Sized> {
read: End,
write: End,
read: Endpoint,
write: Endpoint,
storage: S,
}

Expand All @@ -53,8 +53,8 @@ impl<S: Storage> LocalRb<S> {
assert!(!storage.is_empty());
Self {
storage,
read: End::new(read),
write: End::new(write),
read: Endpoint::new(read),
write: Endpoint::new(write),
}
}
/// Destructures ring buffer into underlying storage and `read` and `write` indices.
Expand Down
7 changes: 4 additions & 3 deletions src/rb/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,16 @@ macro_rules! rb_impl_init {
///
/// *Panics if allocation failed or `capacity` is zero.*
pub fn new(capacity: usize) -> Self {
Self::try_new(capacity).unwrap()
unsafe { Self::from_raw_parts(crate::storage::Heap::<T>::new(capacity), usize::default(), usize::default()) }
}
/// Creates a new instance of a ring buffer returning an error if allocation failed.
///
/// *Panics if `capacity` is zero.*
pub fn try_new(capacity: usize) -> Result<Self, alloc::collections::TryReserveError> {
let mut vec = alloc::vec::Vec::new();
let mut vec = alloc::vec::Vec::<core::mem::MaybeUninit<T>>::new();
vec.try_reserve_exact(capacity)?;
Ok(unsafe { Self::from_raw_parts(vec.into(), usize::default(), usize::default()) })
unsafe { vec.set_len(capacity) };
Ok(unsafe { Self::from_raw_parts(vec.into_boxed_slice().into(), usize::default(), usize::default()) })
}
}

Expand Down
30 changes: 18 additions & 12 deletions src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
use alloc::{boxed::Box, vec::Vec};
use core::{cell::UnsafeCell, marker::PhantomData, mem::MaybeUninit, ops::Range, ptr::NonNull, slice};
#[cfg(feature = "alloc")]
use core::{mem::forget, ptr};
use core::{mem::ManuallyDrop, ptr};

/// Abstract storage for the ring buffer.
///
Expand Down Expand Up @@ -104,7 +104,7 @@ unsafe impl<T, const N: usize> Storage for Array<T, N> {
type Item = T;
#[inline]
fn as_mut_ptr(&self) -> *mut MaybeUninit<T> {
self.data.get() as *mut _
self.data.get().cast()
}
#[inline]
fn len(&self) -> usize {
Expand All @@ -122,7 +122,7 @@ unsafe impl<T> Storage for Slice<T> {
type Item = T;
#[inline]
fn as_mut_ptr(&self) -> *mut MaybeUninit<T> {
self.data.get() as *mut _
self.data.get().cast()
}
#[inline]
fn len(&self) -> usize {
Expand Down Expand Up @@ -153,34 +153,40 @@ unsafe impl<T> Storage for Heap<T> {
}
#[cfg(feature = "alloc")]
impl<T> Heap<T> {
/// Create a new heap storage with exact capacity.
pub fn new(capacity: usize) -> Self {
Self {
ptr: Vec::<T>::with_capacity(capacity).leak() as *mut _ as *mut MaybeUninit<T>,
len: capacity,
}
let mut data = Vec::<MaybeUninit<T>>::with_capacity(capacity);
// `data.capacity()` is not guaranteed to be equal to `capacity`.
// We enforce that by `set_len` and converting to boxed slice.
unsafe { data.set_len(capacity) };
Self::from(data.into_boxed_slice())
}
}
#[cfg(feature = "alloc")]
impl<T> From<Vec<MaybeUninit<T>>> for Heap<T> {
fn from(mut value: Vec<MaybeUninit<T>>) -> Self {
let len = value.capacity();
let ptr = value.as_mut_ptr();
forget(value);
Self { ptr, len }
// Convert `value` to boxed slice of length equals to `value.capacity()`
// except for zero-sized types - for them length will be `value.len()` because `Vec::capacity` for ZST is undefined
// (see <https://doc.rust-lang.org/std/vec/struct.Vec.html#guarantees>).
if size_of::<T>() != 0 {
unsafe { value.set_len(value.capacity()) };
}
Self::from(value.into_boxed_slice())
}
}
#[cfg(feature = "alloc")]
impl<T> From<Box<[MaybeUninit<T>]>> for Heap<T> {
fn from(value: Box<[MaybeUninit<T>]>) -> Self {
Self {
len: value.len(),
ptr: Box::into_raw(value) as *mut MaybeUninit<T>,
ptr: Box::into_raw(value).cast(),
}
}
}
#[cfg(feature = "alloc")]
impl<T> From<Heap<T>> for Box<[MaybeUninit<T>]> {
fn from(value: Heap<T>) -> Self {
let value = ManuallyDrop::new(value);
unsafe { Box::from_raw(ptr::slice_from_raw_parts_mut(value.ptr, value.len)) }
}
}
Expand Down
1 change: 1 addition & 0 deletions src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ mod shared;
mod skip;
mod slice;
mod unsized_;
mod zero_sized;
49 changes: 49 additions & 0 deletions src/tests/zero_sized.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use crate::{
producer::Producer,
traits::{Consumer, Observer, Split},
HeapRb,
};

#[test]
fn basic() {
let (mut prod, mut cons) = HeapRb::<()>::new(2).split();
let obs = prod.observe();
assert_eq!(obs.capacity().get(), 2);

assert_eq!(obs.occupied_len(), 0);
assert_eq!(obs.vacant_len(), 2);
assert!(obs.is_empty());

assert!(cons.try_pop().is_none());

prod.try_push(()).unwrap();
assert_eq!(obs.occupied_len(), 1);
assert_eq!(obs.vacant_len(), 1);

prod.try_push(()).unwrap();
assert_eq!(obs.occupied_len(), 2);
assert_eq!(obs.vacant_len(), 0);
assert!(obs.is_full());

assert!(prod.try_push(()).is_err());

cons.try_pop().unwrap();
assert_eq!(obs.occupied_len(), 1);
assert_eq!(obs.vacant_len(), 1);

prod.try_push(()).unwrap();
assert_eq!(obs.occupied_len(), 2);
assert_eq!(obs.vacant_len(), 0);
assert!(obs.is_full());

cons.try_pop().unwrap();
assert_eq!(obs.occupied_len(), 1);
assert_eq!(obs.vacant_len(), 1);

cons.try_pop().unwrap();
assert_eq!(obs.occupied_len(), 0);
assert_eq!(obs.vacant_len(), 2);
assert!(obs.is_empty());

assert!(cons.try_pop().is_none());
}

0 comments on commit 9cf82af

Please sign in to comment.