Skip to content

Commit ef2ed6a

Browse files
committed
Update tokio::sync::SetOnce with AtomicU8
Update `tokio::sync::SetOnce` struct to internally use a `AtomicU8` to keep track of `AtomicState`, I introduced a `pub(crate) AtomicState` enum to keep readable track of the State. It is a `#[repr(u8)]` enum to keep track of u8 values the state might be in. The drop implementation is changed to use ptr::drop_in_place. We cast the `MaybeUnint<T>` to `*mut T`.
1 parent cc4a0df commit ef2ed6a

File tree

1 file changed

+67
-34
lines changed

1 file changed

+67
-34
lines changed

tokio/src/sync/set_once.rs

Lines changed: 67 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::fmt;
44
use std::mem::MaybeUninit;
55
use std::ops::Drop;
66
use std::ptr;
7-
use std::sync::atomic::{AtomicBool, Ordering};
7+
use std::sync::atomic::{AtomicU8, Ordering};
88

99
// This file contains an implementation of an SetOnce. The value of SetOnce
1010
// can only be modified once during initialization.
@@ -70,11 +70,22 @@ use std::sync::atomic::{AtomicBool, Ordering};
7070
/// }
7171
/// ```
7272
pub struct SetOnce<T> {
73-
value_set: AtomicBool,
73+
// AtomicU8 has 3 states, EMPTY, SETTING and DONE
74+
// is described by the `AtomicState` enum.
75+
value_set: AtomicU8,
7476
value: UnsafeCell<MaybeUninit<T>>,
7577
notify: Notify,
7678
}
7779

80+
#[repr(u8)]
81+
#[derive(PartialEq, Eq)]
82+
pub(crate) enum AtomicState {
83+
Empty = 0,
84+
// SETTING means "set is currently running"
85+
Setting = 1,
86+
Done = 2,
87+
}
88+
7889
impl<T> Default for SetOnce<T> {
7990
fn default() -> SetOnce<T> {
8091
SetOnce::new()
@@ -106,22 +117,22 @@ impl<T: Eq> Eq for SetOnce<T> {}
106117
impl<T> Drop for SetOnce<T> {
107118
fn drop(&mut self) {
108119
if self.initialized() {
109-
// SAFETY: We're inside the drop implementation of SetOnce
110-
// AND we're also initalized. This is the best way to ensure
111-
// out data gets dropped
120+
// SAFETY: Inside the drop implementation of SetOnce also
121+
// initalized. we cast `MaybeUninit<T>` to *mut T
122+
// because `MaybeUninit<T>` doesnt have a destructor
112123
unsafe {
113-
let _ = self.value.with_mut(|ptr| ptr::read(ptr).assume_init());
124+
self.value.with_mut(|ptr| ptr::drop_in_place(ptr as *mut T));
114125
}
115-
// no need to set the flag to false as this set once is being
116-
// dropped
126+
// no need to change value_set here as SetOnce is being dropped
117127
}
118128
}
119129
}
120130

121131
impl<T> From<T> for SetOnce<T> {
122132
fn from(value: T) -> Self {
123133
SetOnce {
124-
value_set: AtomicBool::new(true),
134+
// value has been initialized
135+
value_set: AtomicU8::new(AtomicState::Done as _),
125136
value: UnsafeCell::new(MaybeUninit::new(value)),
126137
notify: Notify::new(),
127138
}
@@ -132,7 +143,7 @@ impl<T> SetOnce<T> {
132143
/// Creates a new empty `SetOnce` instance.
133144
pub fn new() -> Self {
134145
Self {
135-
value_set: AtomicBool::new(false),
146+
value_set: AtomicU8::new(AtomicState::Empty as _),
136147
value: UnsafeCell::new(MaybeUninit::uninit()),
137148
notify: Notify::new(),
138149
}
@@ -174,7 +185,7 @@ impl<T> SetOnce<T> {
174185
#[cfg(not(all(loom, test)))]
175186
pub const fn const_new() -> Self {
176187
Self {
177-
value_set: AtomicBool::new(false),
188+
value_set: AtomicU8::new(AtomicState::Empty as _),
178189
value: UnsafeCell::new(MaybeUninit::uninit()),
179190
notify: Notify::const_new(),
180191
}
@@ -224,7 +235,7 @@ impl<T> SetOnce<T> {
224235
#[cfg(not(all(loom, test)))]
225236
pub const fn const_new_with(value: T) -> Self {
226237
Self {
227-
value_set: AtomicBool::new(true),
238+
value_set: AtomicU8::new(AtomicState::Done as _),
228239
value: UnsafeCell::new(MaybeUninit::new(value)),
229240
notify: Notify::const_new(),
230241
}
@@ -235,7 +246,7 @@ impl<T> SetOnce<T> {
235246
pub fn initialized(&self) -> bool {
236247
// Using acquire ordering so we're able to read/catch any writes that
237248
// are done with `Ordering::Release`
238-
self.value_set.load(Ordering::Acquire)
249+
self.value_set.load(Ordering::Acquire) == AtomicState::Done as _
239250
}
240251

241252
// SAFETY: The SetOnce must not be empty.
@@ -254,13 +265,19 @@ impl<T> SetOnce<T> {
254265
}
255266

256267
// SAFETY: The caller of this function needs to ensure that this function is
257-
// called only when the value_set AtomicBool is flipped from FALSE to TRUE
258-
// meaning that the value is being set from uinitialized to initialized via
259-
// this function
268+
// called only when the self.value_set AtomicUsize is flipped from
269+
// AtomicState::Empty to AtomicState::Setting meaning that the value is
270+
// being set from uinitialized to initialized via this function.
271+
//
272+
// It needs to be done in the order to ensure the safety of this function
273+
// across threads, this function will mutate the value_set to be
274+
// AtomicState::Done.
260275
unsafe fn set_value(&self, value: T) {
261-
unsafe {
262-
self.value.with_mut(|ptr| (*ptr).as_mut_ptr().write(value));
263-
}
276+
self.value.with_mut(|ptr| (*ptr).as_mut_ptr().write(value));
277+
278+
// Store that we're done in Release so any `Acquire` can see
279+
self.value_set
280+
.store(AtomicState::Done as _, Ordering::Release);
264281

265282
// notify the waiting wakers that the value is set
266283
self.notify.notify_waiters();
@@ -282,19 +299,35 @@ impl<T> SetOnce<T> {
282299
return Err(SetError::AlreadyInitializedError(value));
283300
}
284301

285-
// Using release ordering so any threads that read a true from this
286-
// atomic is able to read the value we just stored.
287-
if !self.value_set.swap(true, Ordering::Release) {
288-
// SAFETY: We are swapping the value_set AtomicBool from FALSE to
289-
// TRUE with it being previously false and followed by that we are
290-
// initializing the unsafe Cell field with the value
291-
unsafe {
292-
self.set_value(value);
293-
}
302+
// to capture any writes that are done with `Ordering::Release`
303+
let ordering = Ordering::Acquire;
294304

295-
Ok(())
296-
} else {
297-
Err(SetError::InitializingError(value))
305+
match self.value_set.compare_exchange(
306+
// we expect the set to be uninitialized
307+
AtomicState::Empty as _,
308+
// we want to lock the Set
309+
AtomicState::Setting as _,
310+
ordering,
311+
ordering,
312+
) {
313+
Ok(_) => {
314+
// SAFETY: We changed the state from EMPTY to SETTING,
315+
// this declares the set to be currently running
316+
unsafe {
317+
self.set_value(value);
318+
}
319+
320+
Ok(())
321+
}
322+
Err(true_val) => {
323+
// The value is being currently set in another thread
324+
if true_val == AtomicState::Setting as _ {
325+
Err(SetError::InitializingError(value))
326+
} else {
327+
// here means true_val is either Done or higher
328+
Err(SetError::AlreadyInitializedError(value))
329+
}
330+
}
298331
}
299332
}
300333

@@ -304,9 +337,9 @@ impl<T> SetOnce<T> {
304337
if self.initialized() {
305338
// Since we have taken ownership of self, its drop implementation
306339
// will be called by the end of this function, to prevent a double
307-
// free we will set the value_set to false so that the drop
340+
// free we will set the value_set to EMPTY so that the drop
308341
// implementation does not try to drop the value again.
309-
*self.value_set.get_mut() = false;
342+
*self.value_set.get_mut() = AtomicState::Empty as _;
310343

311344
// SAFETY: The SetOnce is currently initialized, we can assume the
312345
// value is initialized and return that, when we return the value
@@ -337,7 +370,7 @@ impl<T> SetOnce<T> {
337370
// (of type T) across threads.
338371
unsafe impl<T: Sync + Send> Sync for SetOnce<T> {}
339372

340-
// Access to SetOnce's value is guarded by the Atomic boolean flag
373+
// Access to SetOnce's value is guarded by the AtomicUsize state
341374
// and atomic operations on `value_set`, so as long as T itself is Send
342375
// it's safe to send it to another thread
343376
unsafe impl<T: Send> Send for SetOnce<T> {}

0 commit comments

Comments
 (0)