diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1a68c31b51..c24f2f6ff9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Added `FusedIterator` to `vec::IntoIter`, `deque::IntoIter`, `index_map::IntoIter` and `linear_map::IntoIter`.
- Added `ExactSizeIterator` to `vec::IntoIter`, `deque::IntoIter`, `index_map::IntoIter` and `linear_map::IntoIter`.
- Added `DoubleEndedIterator` to `vec::IntoIter` and `deque::IntoIter`.
+- Deprecate `mpmc` (see [#583](https://github.com/rust-embedded/heapless/issues/583#issuecomment-3469297720))
## [v0.9.1] - 2025-08-19
diff --git a/src/mpmc.rs b/src/mpmc.rs
index f676c7c93a..ec36c84be2 100644
--- a/src/mpmc.rs
+++ b/src/mpmc.rs
@@ -1,5 +1,34 @@
//! A fixed capacity multiple-producer, multiple-consumer (MPMC) lock-free queue.
//!
+//! # Deprecation
+//!
+//!
+//! The current implementation of `mpmc` is marked as deprecated due to not being truly lock-free
+//!
+//!
+//! If a thread is parked, or pre-empted for a long time by an higher-priority task
+//! during an `enqueue` or `dequeue` operation, it is possible that the queue ends-up
+//! in a state were no other task can successfully enqueue or dequeue items from it
+//! until the pre-empted task can finish its operation.
+//!
+//! In that case, [`enqueue`](QueueInner::dequeue) and [`dequeue`](QueueInner::enqueue)
+//! will return an error, but will not panic or reach undefined behaviour
+//!
+//! This makes `mpmc` unsuitable for some use cases such as using it as a pool of objects.
+//!
+//! ## When can this queue be used?
+//!
+//! This queue should be used for cross-task communication only when items sent over the queue
+//! can be dropped in case of concurrent operations, or when it is possible to retry
+//! the dequeue/enqueue operation after other tasks have had the opportunity to make progress.
+//!
+//! In that case you can safely ignore the warnings using `#[expect(deprecated)]`
+//! when `new` is called
+//!
+//! For more information, and possible alternative, please see
+//!
+//!
+//!
//! **Note:** This module requires atomic compare-and-swap (CAS) instructions. On
//! targets where they're not natively available, they are emulated by the
//! [`portable-atomic`](https://crates.io/crates/portable-atomic) crate.
@@ -121,7 +150,38 @@ pub type Queue = QueueInner>;
pub type QueueView = QueueInner;
impl Queue {
+ #[deprecated(
+ note = "See the documentation of Queue::new() for more information: https://docs.rs/heapless/latest/heapless/mpmc/type.Queue.html#method.new"
+ )]
/// Creates an empty queue.
+ ///
+ /// # Deprecation
+ ///
+ ///
+ /// The current implementation of `mpmc` is marked as deprecated due to not being truly lock-free
+ ///
+ ///
+ /// If a thread is parked, or pre-empted for a long time by an higher-priority task
+ /// during an `enqueue` or `dequeue` operation, it is possible that the queue ends-up
+ /// in a state were no other task can successfully enqueue or dequeue items from it
+ /// until the pre-empted task can finish its operation.
+ ///
+ /// In that case, [`enqueue`](QueueInner::dequeue) and [`dequeue`](QueueInner::enqueue)
+ /// will return an error, but will not panic or reach undefined behaviour
+ ///
+ /// This makes `mpmc` unsuitable for some use cases such as using it as a pool of objects.
+ ///
+ /// ## When can this queue be used?
+ ///
+ /// This queue should be used for cross-task communication only when items sent over the queue
+ /// can be dropped in case of concurrent operations, or when it is possible to retry
+ /// the dequeue/enqueue operation after other tasks have had the opportunity to make progress.
+ ///
+ /// In that case you can safely ignore the warnings using `#[expect(deprecated)]`
+ /// when `new` is called
+ ///
+ /// For more information, and possible alternative, please see
+ ///
pub const fn new() -> Self {
const {
assert!(N > 1);
@@ -186,6 +246,7 @@ impl QueueInner {
///
/// ```rust
/// # use heapless::mpmc::{Queue, QueueView};
+ /// ##[expect(deprecated)]
/// let mut queue: Queue = Queue::new();
/// let view: &mut QueueView = queue.as_mut_view();
/// ```
@@ -194,6 +255,7 @@ impl QueueInner {
///
/// ```rust
/// # use heapless::mpmc::{Queue, QueueView};
+ /// ##[expect(deprecated)]
/// let mut queue: Queue = Queue::new();
/// let view: &mut QueueView = &mut queue;
/// ```
@@ -228,6 +290,7 @@ impl QueueInner {
impl Default for Queue {
fn default() -> Self {
+ #[allow(deprecated)]
Self::new()
}
}
@@ -355,6 +418,7 @@ mod tests {
fn memory_leak() {
droppable!();
+ #[expect(deprecated)]
let q = Queue::<_, 2>::new();
q.enqueue(Droppable::new()).unwrap_or_else(|_| panic!());
q.enqueue(Droppable::new()).unwrap_or_else(|_| panic!());
@@ -365,6 +429,7 @@ mod tests {
#[test]
fn sanity() {
+ #[expect(deprecated)]
let q = Queue::<_, 2>::new();
q.enqueue(0).unwrap();
q.enqueue(1).unwrap();
@@ -377,6 +442,7 @@ mod tests {
#[test]
fn drain_at_pos255() {
+ #[expect(deprecated)]
let q = Queue::<_, 2>::new();
for _ in 0..255 {
assert!(q.enqueue(0).is_ok());
@@ -389,6 +455,7 @@ mod tests {
#[test]
fn full_at_wrapped_pos0() {
+ #[expect(deprecated)]
let q = Queue::<_, 2>::new();
for _ in 0..254 {
assert!(q.enqueue(0).is_ok());
@@ -408,6 +475,7 @@ mod tests {
#[cfg(feature = "mpmc_large")]
const CAPACITY: usize = 256;
+ #[expect(deprecated)]
let q: Queue = Queue::new();
assert_eq!(q.capacity(), CAPACITY);
diff --git a/tests/tsan.rs b/tests/tsan.rs
index 14391e2435..1f01cb013b 100644
--- a/tests/tsan.rs
+++ b/tests/tsan.rs
@@ -122,6 +122,7 @@ fn mpmc_contention() {
const N: u32 = 64;
+ #[expect(deprecated)]
static Q: Queue = Queue::new();
let (s, r) = mpsc::channel();