matklad / once_cell

Rust library for single assignment cells and lazy statics without macros
Apache License 2.0
1.87k stars 109 forks source link

Provide non blocking subset of sync::OnceCell for no_std #53

Closed matklad closed 2 years ago

matklad commented 5 years ago

OnceCell::get and OnceCell:: set could work without blocking, so we should support these APIs in no std. Note that we intentionally do not want to implement no_std blocking via spinning, see https://www.reddit.com/r/rust/comments/cy910u/comment/eyrfmna for rationale.

matklad commented 5 years ago

Thinking of it, looks like even set can't be lock, in general? You need to atomically set both the is_completed state and the value, and you can only do that if you can pack both into a single word.

You can do that if you heap-allocate the value, but that again needs std.

pitdicker commented 5 years ago

Thinking of it, looks like even set can't be lock, in general? You need to atomically set both the is_completed state and the value, and you can only do that if you can pack both into a single word.

Would one atomic with three states not be enough?

pub struct OnceCell<T> {
    status: AtomicUsize,
    value: UnsafeCell<Option<T>>,
}

pub fn set(&self, value: T) -> Result<(), T> {
    match self.state.load(Ordering::Relaxed) {
        UNINITIALIZED => {
            let old = self.state.compare_and_swap(UNINITIALIZED, RUNNING, Ordering::Relaxed);
            if old != UNINITIALIZED {
                // Another thread must have initialized the cell just now, or is in the process of initializing it.
                Err(value)
            }
            let slot = unsafe { &mut *self.inner.get() };
            *slot = Some(value);
            self.state.store(INITIALIZED, Ordering::Release);
            Ok(())
        },
        RUNNING | INITIALIZED => Err(value),
    }
}

I suppose that if the initializing thread gets suspended while the state is RUNNING, all threads are out of luck until it can continue, but don't really see other problems yet.

matklad commented 5 years ago

Hm, that’s interesting!

I think we perhaps need two methods with set-like semantics: one that might block, but guarantees that the cell is initialized (maybe even returns an &T), and one that does not block, bit also does not guarantee that the cell is initialized.

pitdicker commented 5 years ago

Changing the current set to not block would be a difficult change, as code currently could call get_unchecked after a set and expect it to be safe.

A non blocking set would mean the first get has to be used in a loop or something until it succeeds. I see not blocking as less then optimal, but suppose such a new method can unlock some uses and support no_std.

👍 to have set return &T

matklad commented 2 years ago

This is now more or less availble in the race module