Skip to content

Commit 7ab8038

Browse files
committed
Auto merge of rust-lang#105698 - joboet:unsupported_threads_once, r=thomcc
Use a more efficient `Once` on platforms without threads The current implementation uses an atomic queue and spins rather than panicking when calling `call_once` recursively. Since concurrency is not supported on platforms like WASM, `Once` can be implemented much more efficiently using just a single non-atomic state variable.
2 parents 4653c93 + f9b5684 commit 7ab8038

File tree

6 files changed

+103
-18
lines changed

6 files changed

+103
-18
lines changed

library/std/src/sys/unsupported/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ pub mod fs;
99
pub mod io;
1010
pub mod locks;
1111
pub mod net;
12+
pub mod once;
1213
pub mod os;
1314
#[path = "../unix/os_str.rs"]
1415
pub mod os_str;
+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
use crate::cell::Cell;
2+
use crate::sync as public;
3+
4+
pub struct Once {
5+
state: Cell<State>,
6+
}
7+
8+
pub struct OnceState {
9+
poisoned: bool,
10+
set_state_to: Cell<State>,
11+
}
12+
13+
#[derive(Clone, Copy, PartialEq, Eq)]
14+
enum State {
15+
Incomplete,
16+
Poisoned,
17+
Running,
18+
Complete,
19+
}
20+
21+
struct CompletionGuard<'a> {
22+
state: &'a Cell<State>,
23+
set_state_on_drop_to: State,
24+
}
25+
26+
impl<'a> Drop for CompletionGuard<'a> {
27+
fn drop(&mut self) {
28+
self.state.set(self.set_state_on_drop_to);
29+
}
30+
}
31+
32+
// Safety: threads are not supported on this platform.
33+
unsafe impl Sync for Once {}
34+
35+
impl Once {
36+
#[inline]
37+
#[rustc_const_stable(feature = "const_once_new", since = "1.32.0")]
38+
pub const fn new() -> Once {
39+
Once { state: Cell::new(State::Incomplete) }
40+
}
41+
42+
#[inline]
43+
pub fn is_completed(&self) -> bool {
44+
self.state.get() == State::Complete
45+
}
46+
47+
#[cold]
48+
#[track_caller]
49+
pub fn call(&self, ignore_poisoning: bool, f: &mut impl FnMut(&public::OnceState)) {
50+
let state = self.state.get();
51+
match state {
52+
State::Poisoned if !ignore_poisoning => {
53+
// Panic to propagate the poison.
54+
panic!("Once instance has previously been poisoned");
55+
}
56+
State::Incomplete | State::Poisoned => {
57+
self.state.set(State::Running);
58+
// `guard` will set the new state on drop.
59+
let mut guard =
60+
CompletionGuard { state: &self.state, set_state_on_drop_to: State::Poisoned };
61+
// Run the function, letting it know if we're poisoned or not.
62+
let f_state = public::OnceState {
63+
inner: OnceState {
64+
poisoned: state == State::Poisoned,
65+
set_state_to: Cell::new(State::Complete),
66+
},
67+
};
68+
f(&f_state);
69+
guard.set_state_on_drop_to = f_state.inner.set_state_to.get();
70+
}
71+
State::Running => {
72+
panic!("one-time initialization may not be performed recursively");
73+
}
74+
State::Complete => {}
75+
}
76+
}
77+
}
78+
79+
impl OnceState {
80+
#[inline]
81+
pub fn is_poisoned(&self) -> bool {
82+
self.poisoned
83+
}
84+
85+
#[inline]
86+
pub fn poison(&self) {
87+
self.set_state_to.set(State::Poisoned)
88+
}
89+
}

library/std/src/sys/wasi/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ pub mod io;
3232
#[path = "../unsupported/locks/mod.rs"]
3333
pub mod locks;
3434
pub mod net;
35+
#[path = "../unsupported/once.rs"]
36+
pub mod once;
3537
pub mod os;
3638
#[path = "../unix/os_str.rs"]
3739
pub mod os_str;

library/std/src/sys/wasm/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ cfg_if::cfg_if! {
6666
} else {
6767
#[path = "../unsupported/locks/mod.rs"]
6868
pub mod locks;
69+
#[path = "../unsupported/once.rs"]
70+
pub mod once;
6971
#[path = "../unsupported/thread.rs"]
7072
pub mod thread;
7173
}

library/std/src/sys_common/once/mod.rs

+9-18
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,6 @@
66
// As a result, we end up implementing it ourselves in the standard library.
77
// This also gives us the opportunity to optimize the implementation a bit which
88
// should help the fast path on call sites.
9-
//
10-
// So to recap, the guarantees of a Once are that it will call the
11-
// initialization closure at most once, and it will never return until the one
12-
// that's running has finished running. This means that we need some form of
13-
// blocking here while the custom callback is running at the very least.
14-
// Additionally, we add on the restriction of **poisoning**. Whenever an
15-
// initialization closure panics, the Once enters a "poisoned" state which means
16-
// that all future calls will immediately panic as well.
17-
//
18-
// So to implement this, one might first reach for a `Mutex`, but those cannot
19-
// be put into a `static`. It also gets a lot harder with poisoning to figure
20-
// out when the mutex needs to be deallocated because it's not after the closure
21-
// finishes, but after the first successful closure finishes.
22-
//
23-
// All in all, this is instead implemented with atomics and lock-free
24-
// operations! Whee!
259

2610
cfg_if::cfg_if! {
2711
if #[cfg(any(
@@ -36,8 +20,15 @@ cfg_if::cfg_if! {
3620
))] {
3721
mod futex;
3822
pub use futex::{Once, OnceState};
23+
} else if #[cfg(any(
24+
windows,
25+
target_family = "unix",
26+
all(target_vendor = "fortanix", target_env = "sgx"),
27+
target_os = "solid_asp3",
28+
))] {
29+
mod queue;
30+
pub use queue::{Once, OnceState};
3931
} else {
40-
mod generic;
41-
pub use generic::{Once, OnceState};
32+
pub use crate::sys::once::{Once, OnceState};
4233
}
4334
}

0 commit comments

Comments
 (0)