From d3bf5a5424dae61a802d67fb5ba94b9ac8e02d1d Mon Sep 17 00:00:00 2001 From: John Nunley Date: Wed, 24 Aug 2022 10:09:13 -0700 Subject: [PATCH] Make it so this crate can be `no_std` (#22) --- .github/workflows/ci.yml | 8 ++++++++ Cargo.toml | 4 ++++ src/bounded.rs | 14 +++++++------- src/lib.rs | 36 ++++++++++++++++++++++++++++++++++-- src/single.rs | 11 +++++------ src/unbounded.rs | 22 +++++++++++----------- 6 files changed, 69 insertions(+), 26 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8fc05a8..d5fb135 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,6 +29,10 @@ jobs: if: startsWith(matrix.rust, 'nightly') run: cargo check -Z features=dev_dep - run: cargo test + - name: Install cargo-hack + uses: taiki-e/install-action@cargo-hack + - run: rustup target add thumbv7m-none-eabi + - run: cargo hack build --target thumbv7m-none-eabi --no-default-features --no-dev-deps msrv: runs-on: ubuntu-latest @@ -42,6 +46,10 @@ jobs: - name: Install Rust run: rustup update ${{ matrix.rust }} && rustup default ${{ matrix.rust }} - run: cargo build + - name: Install cargo-hack + uses: taiki-e/install-action@cargo-hack + - run: rustup target add thumbv7m-none-eabi + - run: cargo hack build --target thumbv7m-none-eabi --no-default-features --no-dev-deps clippy: runs-on: ubuntu-latest diff --git a/Cargo.toml b/Cargo.toml index 030b928..d7908ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,3 +28,7 @@ harness = false criterion = "0.3.4" easy-parallel = "3.1.0" fastrand = "1.3.3" + +[features] +default = ["std"] +std = [] diff --git a/src/bounded.rs b/src/bounded.rs index 781038b..28db25d 100644 --- a/src/bounded.rs +++ b/src/bounded.rs @@ -1,11 +1,11 @@ -use std::cell::UnsafeCell; -use std::mem::MaybeUninit; -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::thread; +use alloc::{boxed::Box, vec::Vec}; +use core::cell::UnsafeCell; +use core::mem::MaybeUninit; +use core::sync::atomic::{AtomicUsize, Ordering}; use cache_padded::CachePadded; -use crate::{PopError, PushError}; +use crate::{busy_wait, PopError, PushError}; /// A slot in a queue. struct Slot { @@ -141,7 +141,7 @@ impl Bounded { tail = self.tail.load(Ordering::Relaxed); } else { // Yield because we need to wait for the stamp to get updated. - thread::yield_now(); + busy_wait(); tail = self.tail.load(Ordering::Relaxed); } } @@ -207,7 +207,7 @@ impl Bounded { head = self.head.load(Ordering::Relaxed); } else { // Yield because we need to wait for the stamp to get updated. - thread::yield_now(); + busy_wait(); head = self.head.load(Ordering::Relaxed); } } diff --git a/src/lib.rs b/src/lib.rs index d54cee5..6531abe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,16 +24,32 @@ //! assert_eq!(q.pop(), Ok(2)); //! ``` //! +//! # Features +//! +//! `concurrent-queue` used an `std` default feature. With this feature enabled, this crate will +//! use [`std::thread::yield_now`] to avoid busy waiting in tight loops. However, with this +//! feature disabled, [`core::hint::spin_loop`] will be used instead. Disabling `std` will allow +//! this crate to be used on `no_std` platforms at the potential expense of more busy waiting. +//! //! [Bounded]: `ConcurrentQueue::bounded()` //! [Unbounded]: `ConcurrentQueue::unbounded()` //! [closed]: `ConcurrentQueue::close()` #![warn(missing_docs, missing_debug_implementations, rust_2018_idioms)] +#![no_std] +extern crate alloc; +#[cfg(feature = "std")] +extern crate std; + +use alloc::boxed::Box; +use core::fmt; +use core::sync::atomic::{self, AtomicUsize, Ordering}; + +#[cfg(feature = "std")] use std::error; -use std::fmt; +#[cfg(feature = "std")] use std::panic::{RefUnwindSafe, UnwindSafe}; -use std::sync::atomic::{self, AtomicUsize, Ordering}; use crate::bounded::Bounded; use crate::single::Single; @@ -65,7 +81,9 @@ pub struct ConcurrentQueue(Inner); unsafe impl Send for ConcurrentQueue {} unsafe impl Sync for ConcurrentQueue {} +#[cfg(feature = "std")] impl UnwindSafe for ConcurrentQueue {} +#[cfg(feature = "std")] impl RefUnwindSafe for ConcurrentQueue {} enum Inner { @@ -367,6 +385,7 @@ impl PopError { } } +#[cfg(feature = "std")] impl error::Error for PopError {} impl fmt::Debug for PopError { @@ -423,6 +442,7 @@ impl PushError { } } +#[cfg(feature = "std")] impl error::Error for PushError {} impl fmt::Debug for PushError { @@ -443,6 +463,18 @@ impl fmt::Display for PushError { } } +/// Notify the CPU that we are currently busy-waiting. +#[inline] +fn busy_wait() { + #[cfg(feature = "std")] + std::thread::yield_now(); + // Use the deprecated `spin_loop_hint` here in order to + // avoid bumping the MSRV. + #[allow(deprecated)] + #[cfg(not(feature = "std"))] + core::sync::atomic::spin_loop_hint() +} + /// Equivalent to `atomic::fence(Ordering::SeqCst)`, but in some cases faster. #[inline] fn full_fence() { diff --git a/src/single.rs b/src/single.rs index 26562e9..06ab766 100644 --- a/src/single.rs +++ b/src/single.rs @@ -1,9 +1,8 @@ -use std::cell::UnsafeCell; -use std::mem::MaybeUninit; -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::thread; +use core::cell::UnsafeCell; +use core::mem::MaybeUninit; +use core::sync::atomic::{AtomicUsize, Ordering}; -use crate::{PopError, PushError}; +use crate::{busy_wait, PopError, PushError}; const LOCKED: usize = 1 << 0; const PUSHED: usize = 1 << 1; @@ -77,7 +76,7 @@ impl Single { if prev & LOCKED == 0 { state = prev; } else { - thread::yield_now(); + busy_wait(); state = prev & !LOCKED; } } diff --git a/src/unbounded.rs b/src/unbounded.rs index 9018778..abf406c 100644 --- a/src/unbounded.rs +++ b/src/unbounded.rs @@ -1,12 +1,12 @@ -use std::cell::UnsafeCell; -use std::mem::MaybeUninit; -use std::ptr; -use std::sync::atomic::{AtomicPtr, AtomicUsize, Ordering}; -use std::thread; +use alloc::boxed::Box; +use core::cell::UnsafeCell; +use core::mem::MaybeUninit; +use core::ptr; +use core::sync::atomic::{AtomicPtr, AtomicUsize, Ordering}; use cache_padded::CachePadded; -use crate::{PopError, PushError}; +use crate::{busy_wait, PopError, PushError}; // Bits indicating the state of a slot: // * If a value has been written into the slot, `WRITE` is set. @@ -45,7 +45,7 @@ impl Slot { /// Waits until a value is written into the slot. fn wait_write(&self) { while self.state.load(Ordering::Acquire) & WRITE == 0 { - thread::yield_now(); + busy_wait(); } } } @@ -77,7 +77,7 @@ impl Block { if !next.is_null() { return next; } - thread::yield_now(); + busy_wait(); } } @@ -152,7 +152,7 @@ impl Unbounded { // If we reached the end of the block, wait until the next one is installed. if offset == BLOCK_CAP { - thread::yield_now(); + busy_wait(); tail = self.tail.index.load(Ordering::Acquire); block = self.tail.block.load(Ordering::Acquire); continue; @@ -228,7 +228,7 @@ impl Unbounded { // If we reached the end of the block, wait until the next one is installed. if offset == BLOCK_CAP { - thread::yield_now(); + busy_wait(); head = self.head.index.load(Ordering::Acquire); block = self.head.block.load(Ordering::Acquire); continue; @@ -258,7 +258,7 @@ impl Unbounded { // The block can be null here only if the first push operation is in progress. if block.is_null() { - thread::yield_now(); + busy_wait(); head = self.head.index.load(Ordering::Acquire); block = self.head.block.load(Ordering::Acquire); continue;