diff --git a/Cargo.toml b/Cargo.toml index a9f8c495f..e04164ae5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,6 @@ rust-version = "1.67" [dependencies] jobserver = { version = "0.1.30", default-features = false, optional = true } -once_cell = { version = "1.19", optional = true } [target.'cfg(unix)'.dependencies] # Don't turn on the feature "std" for this, see https://github.com/rust-lang/cargo/issues/4866 @@ -29,7 +28,7 @@ once_cell = { version = "1.19", optional = true } libc = { version = "0.2.62", default-features = false, optional = true } [features] -parallel = ["dep:libc", "dep:jobserver", "dep:once_cell"] +parallel = ["dep:libc", "dep:jobserver"] # This is a placeholder feature for people who incorrectly used `cc` with `features = ["jobserver"]` # so that they aren't broken. This has never enabled `parallel`, so we won't do that. jobserver = [] diff --git a/src/lib.rs b/src/lib.rs index 05d6f6932..ca28d3f7c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1502,7 +1502,7 @@ impl Build { } // Limit our parallelism globally with a jobserver. - let tokens = parallel::job_token::ActiveJobTokenServer::new(); + let mut tokens = parallel::job_token::ActiveJobTokenServer::new(); // When compiling objects in parallel we do a few dirty tricks to speed // things up: diff --git a/src/parallel/job_token.rs b/src/parallel/job_token.rs index da1ab737a..2640cea6c 100644 --- a/src/parallel/job_token.rs +++ b/src/parallel/job_token.rs @@ -2,7 +2,7 @@ use std::marker::PhantomData; use crate::Error; -use once_cell::sync::OnceCell; +use super::once_lock::OnceLock; pub(crate) struct JobToken(PhantomData<()>); @@ -37,7 +37,7 @@ impl JobTokenServer { /// compilation. fn new() -> &'static Self { // TODO: Replace with a OnceLock once MSRV is 1.70 - static JOBSERVER: OnceCell = OnceCell::new(); + static JOBSERVER: OnceLock = OnceLock::new(); JOBSERVER.get_or_init(|| { unsafe { inherited_jobserver::JobServer::from_env() } @@ -62,8 +62,8 @@ impl ActiveJobTokenServer { } } - pub(crate) async fn acquire(&self) -> Result { - match &self { + pub(crate) async fn acquire(&mut self) -> Result { + match self { Self::Inherited(jobserver) => jobserver.acquire().await, Self::InProcess(jobserver) => Ok(jobserver.acquire().await), } @@ -71,7 +71,7 @@ impl ActiveJobTokenServer { } mod inherited_jobserver { - use super::{JobToken, OnceCell}; + use super::JobToken; use crate::{parallel::async_executor::YieldOnce, Error, ErrorKind}; @@ -137,7 +137,7 @@ mod inherited_jobserver { pub(super) fn enter_active(&self) -> ActiveJobServer<'_> { ActiveJobServer { jobserver: self, - helper_thread: OnceCell::new(), + helper_thread: None, } } } @@ -163,11 +163,11 @@ mod inherited_jobserver { pub(crate) struct ActiveJobServer<'a> { jobserver: &'a JobServer, - helper_thread: OnceCell, + helper_thread: Option, } impl<'a> ActiveJobServer<'a> { - pub(super) async fn acquire(&self) -> Result { + pub(super) async fn acquire(&mut self) -> Result { let mut has_requested_token = false; loop { @@ -184,9 +184,12 @@ mod inherited_jobserver { Ok(None) => YieldOnce::default().await, Err(err) if err.kind() == io::ErrorKind::Unsupported => { // Fallback to creating a help thread with blocking acquire - let helper_thread = self - .helper_thread - .get_or_try_init(|| HelperThread::new(self.jobserver))?; + let helper_thread = if let Some(thread) = self.helper_thread.as_ref() { + thread + } else { + self.helper_thread + .insert(HelperThread::new(self.jobserver)?) + }; match helper_thread.rx.try_recv() { Ok(res) => { diff --git a/src/parallel/mod.rs b/src/parallel/mod.rs index 019eae102..70a56e74d 100644 --- a/src/parallel/mod.rs +++ b/src/parallel/mod.rs @@ -1,3 +1,4 @@ pub(crate) mod async_executor; pub(crate) mod job_token; +pub(crate) mod once_lock; pub(crate) mod stderr; diff --git a/src/parallel/once_lock.rs b/src/parallel/once_lock.rs new file mode 100644 index 000000000..c48dbb72e --- /dev/null +++ b/src/parallel/once_lock.rs @@ -0,0 +1,47 @@ +use std::{ + cell::UnsafeCell, + marker::PhantomData, + mem::MaybeUninit, + panic::{RefUnwindSafe, UnwindSafe}, + sync::Once, +}; + +pub(crate) struct OnceLock { + once: Once, + value: UnsafeCell>, + _marker: PhantomData, +} + +impl OnceLock { + pub(crate) const fn new() -> Self { + Self { + once: Once::new(), + value: UnsafeCell::new(MaybeUninit::uninit()), + _marker: PhantomData, + } + } + + pub(crate) fn get_or_init(&self, f: impl FnOnce() -> T) -> &T { + self.once.call_once(|| { + unsafe { &mut *self.value.get() }.write(f()); + }); + unsafe { (&*self.value.get()).assume_init_ref() } + } +} + +unsafe impl Sync for OnceLock {} +unsafe impl Send for OnceLock {} + +impl RefUnwindSafe for OnceLock {} +impl UnwindSafe for OnceLock {} + +impl Drop for OnceLock { + #[inline] + fn drop(&mut self) { + if self.once.is_completed() { + // SAFETY: The cell is initialized and being dropped, so it can't + // be accessed again. + unsafe { self.value.get_mut().assume_init_drop() }; + } + } +}