|  | use std::sync::{Arc, LazyLock, OnceLock}; | 
|  |  | 
|  | pub use jobserver_crate::{Acquired, Client, HelperThread}; | 
|  | use jobserver_crate::{FromEnv, FromEnvErrorKind}; | 
|  | use parking_lot::{Condvar, Mutex}; | 
|  |  | 
|  | // We can only call `from_env_ext` once per process | 
|  |  | 
|  | // We stick this in a global because there could be multiple rustc instances | 
|  | // in this process, and the jobserver is per-process. | 
|  | static GLOBAL_CLIENT: LazyLock<Result<Client, String>> = LazyLock::new(|| { | 
|  | // Note that this is unsafe because it may misinterpret file descriptors | 
|  | // on Unix as jobserver file descriptors. We hopefully execute this near | 
|  | // the beginning of the process though to ensure we don't get false | 
|  | // positives, or in other words we try to execute this before we open | 
|  | // any file descriptors ourselves. | 
|  | let FromEnv { client, var } = unsafe { Client::from_env_ext(true) }; | 
|  |  | 
|  | let error = match client { | 
|  | Ok(client) => return Ok(client), | 
|  | Err(e) => e, | 
|  | }; | 
|  |  | 
|  | if matches!( | 
|  | error.kind(), | 
|  | FromEnvErrorKind::NoEnvVar | 
|  | | FromEnvErrorKind::NoJobserver | 
|  | | FromEnvErrorKind::NegativeFd | 
|  | | FromEnvErrorKind::Unsupported | 
|  | ) { | 
|  | return Ok(default_client()); | 
|  | } | 
|  |  | 
|  | // Environment specifies jobserver, but it looks incorrect. | 
|  | // Safety: `error.kind()` should be `NoEnvVar` if `var == None`. | 
|  | let (name, value) = var.unwrap(); | 
|  | Err(format!( | 
|  | "failed to connect to jobserver from environment variable `{name}={:?}`: {error}", | 
|  | value | 
|  | )) | 
|  | }); | 
|  |  | 
|  | // Create a new jobserver if there's no inherited one. | 
|  | fn default_client() -> Client { | 
|  | // Pick a "reasonable maximum" capping out at 32 | 
|  | // so we don't take everything down by hogging the process run queue. | 
|  | // The fixed number is used to have deterministic compilation across machines. | 
|  | let client = Client::new(32).expect("failed to create jobserver"); | 
|  |  | 
|  | // Acquire a token for the main thread which we can release later | 
|  | client.acquire_raw().ok(); | 
|  |  | 
|  | client | 
|  | } | 
|  |  | 
|  | static GLOBAL_CLIENT_CHECKED: OnceLock<Client> = OnceLock::new(); | 
|  |  | 
|  | pub fn initialize_checked(report_warning: impl FnOnce(&'static str)) { | 
|  | let client_checked = match &*GLOBAL_CLIENT { | 
|  | Ok(client) => client.clone(), | 
|  | Err(e) => { | 
|  | report_warning(e); | 
|  | default_client() | 
|  | } | 
|  | }; | 
|  | GLOBAL_CLIENT_CHECKED.set(client_checked).ok(); | 
|  | } | 
|  |  | 
|  | const ACCESS_ERROR: &str = "jobserver check should have been called earlier"; | 
|  |  | 
|  | pub fn client() -> Client { | 
|  | GLOBAL_CLIENT_CHECKED.get().expect(ACCESS_ERROR).clone() | 
|  | } | 
|  |  | 
|  | struct ProxyData { | 
|  | /// The number of tokens assigned to threads. | 
|  | /// If this is 0, a single token is still assigned to this process, but is unused. | 
|  | used: u16, | 
|  |  | 
|  | /// The number of threads requesting a token | 
|  | pending: u16, | 
|  | } | 
|  |  | 
|  | /// This is a jobserver proxy used to ensure that we hold on to at least one token. | 
|  | pub struct Proxy { | 
|  | client: Client, | 
|  | data: Mutex<ProxyData>, | 
|  |  | 
|  | /// Threads which are waiting on a token will wait on this. | 
|  | wake_pending: Condvar, | 
|  |  | 
|  | helper: OnceLock<HelperThread>, | 
|  | } | 
|  |  | 
|  | impl Proxy { | 
|  | pub fn new() -> Arc<Self> { | 
|  | let proxy = Arc::new(Proxy { | 
|  | client: client(), | 
|  | data: Mutex::new(ProxyData { used: 1, pending: 0 }), | 
|  | wake_pending: Condvar::new(), | 
|  | helper: OnceLock::new(), | 
|  | }); | 
|  | let proxy_ = Arc::clone(&proxy); | 
|  | let helper = proxy | 
|  | .client | 
|  | .clone() | 
|  | .into_helper_thread(move |token| { | 
|  | if let Ok(token) = token { | 
|  | let mut data = proxy_.data.lock(); | 
|  | if data.pending > 0 { | 
|  | // Give the token to a waiting thread | 
|  | token.drop_without_releasing(); | 
|  | assert!(data.used > 0); | 
|  | data.used += 1; | 
|  | data.pending -= 1; | 
|  | proxy_.wake_pending.notify_one(); | 
|  | } else { | 
|  | // The token is no longer needed, drop it. | 
|  | drop(data); | 
|  | drop(token); | 
|  | } | 
|  | } | 
|  | }) | 
|  | .expect("failed to create helper thread"); | 
|  | proxy.helper.set(helper).unwrap(); | 
|  | proxy | 
|  | } | 
|  |  | 
|  | pub fn acquire_thread(&self) { | 
|  | let mut data = self.data.lock(); | 
|  |  | 
|  | if data.used == 0 { | 
|  | // There was a free token around. This can | 
|  | // happen when all threads release their token. | 
|  | assert_eq!(data.pending, 0); | 
|  | data.used += 1; | 
|  | } else { | 
|  | // Request a token from the helper thread. We can't directly use `acquire_raw` | 
|  | // as we also need to be able to wait for the final token in the process which | 
|  | // does not get a corresponding `release_raw` call. | 
|  | self.helper.get().unwrap().request_token(); | 
|  | data.pending += 1; | 
|  | self.wake_pending.wait(&mut data); | 
|  | } | 
|  | } | 
|  |  | 
|  | pub fn release_thread(&self) { | 
|  | let mut data = self.data.lock(); | 
|  |  | 
|  | if data.pending > 0 { | 
|  | // Give the token to a waiting thread | 
|  | data.pending -= 1; | 
|  | self.wake_pending.notify_one(); | 
|  | } else { | 
|  | data.used -= 1; | 
|  |  | 
|  | // Release the token unless it's the last one in the process | 
|  | if data.used > 0 { | 
|  | drop(data); | 
|  | self.client.release_raw().ok(); | 
|  | } | 
|  | } | 
|  | } | 
|  | } |