Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add no-threads mode, for use in single-threaded environments #82

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ regex = "1.2.0"
default = ["std"]
# Enables `once_cell::sync` module.
std = []
# Enables `sync` module for `no_std`, assuming that no concurrent initialization of `OnceCell` can happen.
# This is mostly intended for WASM.
# If concurrent initialization does happen, the result is a panic.
assume-no-threads-or-interrupts = []

[[example]]
name = "bench"
Expand Down
24 changes: 20 additions & 4 deletions src/imp_std.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@
// * no poisoning
// * init function can fail

use core::{
cell::UnsafeCell,
sync::atomic::{AtomicUsize, Ordering},
};

#[cfg(feature = "std")]
use std::{
cell::{Cell, UnsafeCell},
cell::Cell,
marker::PhantomData,
panic::{RefUnwindSafe, UnwindSafe},
sync::atomic::{AtomicBool, AtomicUsize, Ordering},
sync::atomic::AtomicBool,
thread::{self, Thread},
};

Expand All @@ -16,12 +22,13 @@ pub(crate) struct OnceCell<T> {
// This `state` word is actually an encoded version of just a pointer to a
// `Waiter`, so we add the `PhantomData` appropriately.
state_and_queue: AtomicUsize,
_marker: PhantomData<*mut Waiter>,
// FIXME: switch to `std::mem::MaybeUninit` once we are ready to bump MSRV
// that far. It was stabilized in 1.36.0, so, if you are reading this and
// it's higher than 1.46.0 outside, please send a PR! ;) (and do the same
// for `Lazy`, while we are at it).
pub(crate) value: UnsafeCell<Option<T>>,
#[cfg(feature = "std")]
_marker: PhantomData<*mut Waiter>,
}

// Why do we need `T: Send`?
Expand All @@ -32,7 +39,9 @@ pub(crate) struct OnceCell<T> {
unsafe impl<T: Sync + Send> Sync for OnceCell<T> {}
unsafe impl<T: Send> Send for OnceCell<T> {}

#[cfg(feature = "std")]
impl<T: RefUnwindSafe + UnwindSafe> RefUnwindSafe for OnceCell<T> {}
#[cfg(feature = "std")]
impl<T: UnwindSafe> UnwindSafe for OnceCell<T> {}

// Three states that a OnceCell can be in, encoded into the lower bits of `state` in
Expand All @@ -46,6 +55,7 @@ const COMPLETE: usize = 0x2;
const STATE_MASK: usize = 0x3;

// Representation of a node in the linked list of waiters in the RUNNING state.
#[cfg(feature = "std")]
#[repr(align(4))] // Ensure the two lower bits are free to use as state bits.
struct Waiter {
thread: Cell<Option<Thread>>,
Expand All @@ -65,8 +75,9 @@ impl<T> OnceCell<T> {
pub(crate) const fn new() -> OnceCell<T> {
OnceCell {
state_and_queue: AtomicUsize::new(INCOMPLETE),
_marker: PhantomData,
value: UnsafeCell::new(None),
#[cfg(feature = "std")]
_marker: PhantomData,
}
}

Expand Down Expand Up @@ -136,6 +147,9 @@ fn initialize_inner(my_state_and_queue: &AtomicUsize, init: &mut dyn FnMut() ->
waiter_queue.set_state_on_drop_to = if success { COMPLETE } else { INCOMPLETE };
return success;
}
#[cfg(not(feature = "std"))]
_ => panic!("assume-no-threads-or-interrupts is set, but there are threads!"),
#[cfg(feature = "std")]
_ => {
assert!(state_and_queue & STATE_MASK == RUNNING);
wait(&my_state_and_queue, state_and_queue);
Expand All @@ -146,6 +160,7 @@ fn initialize_inner(my_state_and_queue: &AtomicUsize, init: &mut dyn FnMut() ->
}

// Copy-pasted from std exactly.
#[cfg(feature = "std")]
fn wait(state_and_queue: &AtomicUsize, mut current_state: usize) {
loop {
if current_state & STATE_MASK != RUNNING {
Expand Down Expand Up @@ -180,6 +195,7 @@ impl Drop for WaiterQueue<'_> {

assert_eq!(state_and_queue & STATE_MASK, RUNNING);

#[cfg(feature = "std")]
unsafe {
let mut queue = (state_and_queue & !STATE_MASK) as *const Waiter;
while !queue.is_null() {
Expand Down
10 changes: 6 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ might be easier to debug than a deadlock.
#[path = "imp_pl.rs"]
mod imp;

#[cfg(feature = "std")]
#[cfg(any(feature = "std", feature = "assume-no-threads-or-interrupts"))]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice if we could just declare the imp module inside the sync module, so we don't have to worry about these cfgs getting out of sync.

However, rust-lang/rust#35016 is still not fixed, so the only way to do this is moving the imp_*.rs files (which seems like a hassle).

#[cfg(not(feature = "parking_lot"))]
#[path = "imp_std.rs"]
mod imp;
Expand Down Expand Up @@ -558,9 +558,11 @@ pub mod unsync {
}
}

#[cfg(feature = "std")]
#[cfg(any(feature = "std", feature = "assume-no-threads-or-interrupts"))]
pub mod sync {
use std::{cell::Cell, fmt, hint::unreachable_unchecked, panic::RefUnwindSafe};
use core::{cell::Cell, fmt, hint::unreachable_unchecked, ops::Deref};
#[cfg(feature = "std")]
use std::panic::RefUnwindSafe;

use crate::imp::OnceCell as Imp;

Expand Down Expand Up @@ -895,7 +897,7 @@ pub mod sync {
}
}

impl<T, F: FnOnce() -> T> ::std::ops::Deref for Lazy<T, F> {
impl<T, F: FnOnce() -> T> Deref for Lazy<T, F> {
type Target = T;
fn deref(&self) -> &T {
Lazy::force(self)
Expand Down