diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bea10baa6..24e4a0960f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Added `VecView`, the `!Sized` version of `Vec`. - Added pool implementations for 64-bit architectures. - Added `IntoIterator` implementation for `LinearMap` +- Added `MpMcQueueView`, the `!Sized` version of `MpMcQueue`. ### Changed @@ -31,6 +32,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Fixed the list of implemented data structures in the crate docs, by adding `Deque`, `HistoryBuffer` and `SortedLinkedList` to the list. - Fixed `MpMcQueue` with `mpmc_large` feature. +- Fix missing `Drop` for `MpMcQueue` ## [v0.8.0] - 2023-11-07 diff --git a/src/mpmc.rs b/src/mpmc.rs index 3da72d7f31..9cc826dd44 100644 --- a/src/mpmc.rs +++ b/src/mpmc.rs @@ -128,17 +128,77 @@ pub type Q32 = MpMcQueue; /// MPMC queue with a capability for 64 elements. pub type Q64 = MpMcQueue; +mod private { + use core::marker::PhantomData; + + use super::*; + + ///
This is private API and should not be used
+ pub struct Owned(PhantomData<[T; N]>); + ///
This is private API and should not be used
+ pub struct View(PhantomData<[T]>); + + ///
This is private API and should not be used
+ pub struct MpMcQueueInner { + pub(super) dequeue_pos: AtomicTargetSize, + pub(super) enqueue_pos: AtomicTargetSize, + pub(super) buffer: UnsafeCell, + } + pub trait MpMcQueueBuffer { + type Buffer: ?Sized; + type T; + + fn as_view(this: &private::MpMcQueueInner) -> &MpMcQueueView; + fn as_mut_view(this: &mut private::MpMcQueueInner) -> &mut MpMcQueueView; + } + + impl MpMcQueueBuffer for private::Owned { + type Buffer = [Cell; N]; + type T = T; + fn as_view(this: &private::MpMcQueueInner) -> &MpMcQueueView { + this + } + fn as_mut_view(this: &mut private::MpMcQueueInner) -> &mut MpMcQueueView { + this + } + } + + impl MpMcQueueBuffer for private::View { + type Buffer = [Cell]; + type T = T; + fn as_view(this: &private::MpMcQueueInner) -> &MpMcQueueView { + this + } + fn as_mut_view(this: &mut private::MpMcQueueInner) -> &mut MpMcQueueView { + this + } + } + + // Cell is sealed to satisfy the compiler's requirement of not leaking private types through the MpMcQueueBuffer trait implementations + pub struct Cell { + pub(super) data: MaybeUninit, + pub(super) sequence: AtomicTargetSize, + } +} + +// Workaround https://github.com/rust-lang/rust/issues/119015. This is required so that the methods on `VecView` and `Vec` are properly documented. +// cfg(doc) prevents `MpMcQueueInner` being part of the public API. +// doc(hidden) prevents the `pub use vec::VecInner` from being visible in the documentation. +#[cfg(doc)] +#[doc(hidden)] +pub use private::MpMcQueueInner as _; + +use private::Cell; + /// MPMC queue with a capacity for N elements /// N must be a power of 2 /// The max value of N is u8::MAX - 1 if `mpmc_large` feature is not enabled. -pub struct MpMcQueue { - buffer: UnsafeCell<[Cell; N]>, - dequeue_pos: AtomicTargetSize, - enqueue_pos: AtomicTargetSize, -} +pub type MpMcQueue = private::MpMcQueueInner>; + +/// MPMC queue with a capacity for a dynamic number of element +pub type MpMcQueueView = private::MpMcQueueInner>; impl MpMcQueue { - const MASK: UintSize = (N - 1) as UintSize; const EMPTY_CELL: Cell = Cell::new(0); const ASSERT: [(); 1] = [()]; @@ -170,7 +230,69 @@ impl MpMcQueue { /// Returns the item in the front of the queue, or `None` if the queue is empty pub fn dequeue(&self) -> Option { - unsafe { dequeue(self.buffer.get() as *mut _, &self.dequeue_pos, Self::MASK) } + self.as_view().dequeue() + } + + /// Adds an `item` to the end of the queue + /// + /// Returns back the `item` if the queue is full + pub fn enqueue(&self, item: T) -> Result<(), T> { + self.as_view().enqueue(item) + } + + /// Get a reference to the `MpMcQueue`, erasing the `N` const-generic. + /// + /// ```rust + /// # use heapless::mpmc::{MpMcQueue, MpMcQueueView, Q2}; + /// let q: MpMcQueue = Q2::new(); + /// let view: &MpMcQueueView = q.as_view(); + /// ``` + /// + /// It is often preferable to do the same through type coerction, since `MpMcQueue` implements `Unsize>`: + /// + /// ```rust + /// # use heapless::mpmc::{MpMcQueue, MpMcQueueView, Q2}; + /// let q: MpMcQueue = Q2::new(); + /// let view: &MpMcQueueView = &q; + /// ``` + pub fn as_view(&self) -> &MpMcQueueView { + self + } + + /// Get a mutable reference to the `MpMcQueue`, erasing the `N` const-generic. + /// + /// ```rust + /// # use heapless::mpmc::{MpMcQueue, MpMcQueueView, Q2}; + /// let mut q: MpMcQueue = Q2::new(); + /// let view: &mut MpMcQueueView = q.as_mut_view(); + /// ``` + /// + /// It is often preferable to do the same through type coerction, since `MpMcQueue` implements `Unsize>`: + /// + /// ```rust + /// # use heapless::mpmc::{MpMcQueue, MpMcQueueView, Q2}; + /// let mut q: MpMcQueue = Q2::new(); + /// let view: &mut MpMcQueueView = &mut q; + /// ``` + pub fn as_mut_view(&mut self) -> &mut MpMcQueueView { + self + } +} + +impl MpMcQueueView { + fn mask(&self) -> UintSize { + // We get the len of the buffer. There is now revireversee method of `core::ptr::from_raw_parts`, so we have to work around it. + let ptr: *const [()] = self.buffer.get() as _; + // SAFETY: There is no aliasing as () is zero-sized + let slice: &[()] = unsafe { &*ptr }; + let len = slice.len(); + + (len - 1) as _ + } + + /// Returns the item in the front of the queue, or `None` if the queue is empty + pub fn dequeue(&self) -> Option { + unsafe { dequeue(self.buffer.get() as *mut _, &self.dequeue_pos, self.mask()) } } /// Adds an `item` to the end of the queue @@ -181,7 +303,7 @@ impl MpMcQueue { enqueue( self.buffer.get() as *mut _, &self.enqueue_pos, - Self::MASK, + self.mask(), item, ) } @@ -194,13 +316,17 @@ impl Default for MpMcQueue { } } -unsafe impl Sync for MpMcQueue where T: Send {} - -struct Cell { - data: MaybeUninit, - sequence: AtomicTargetSize, +impl Drop for private::MpMcQueueInner { + fn drop(&mut self) { + let this = B::as_mut_view(self); + // drop all contents currently in the queue + while this.dequeue().is_some() {} + } } +unsafe impl Sync for MpMcQueue where T: Send {} +unsafe impl Sync for MpMcQueueView where T: Send {} + impl Cell { const fn new(seq: usize) -> Self { Self { @@ -306,6 +432,18 @@ mod tests { // Ensure a `MpMcQueue` containing `!Send` values stays `!Send` itself. assert_not_impl_any!(MpMcQueue<*const (), 4>: Send); + #[test] + fn memory_leak() { + droppable!(); + + let q = Q2::new(); + q.enqueue(Droppable::new()).unwrap_or_else(|_| panic!()); + q.enqueue(Droppable::new()).unwrap_or_else(|_| panic!()); + drop(q); + + assert_eq!(Droppable::count(), 0); + } + #[test] fn sanity() { let q = Q2::new();