2022-08-23 15:44:39 +00:00
|
|
|
use std::sync::{Arc, Condvar, Mutex};
|
|
|
|
|
|
|
|
use rustc_session::Session;
|
|
|
|
|
|
|
|
use jobserver::HelperThread;
|
|
|
|
|
2022-08-24 14:28:40 +00:00
|
|
|
// FIXME don't panic when a worker thread panics
|
|
|
|
|
2022-08-23 15:44:39 +00:00
|
|
|
pub(super) struct ConcurrencyLimiter {
|
|
|
|
helper_thread: Option<HelperThread>,
|
|
|
|
state: Arc<Mutex<state::ConcurrencyLimiterState>>,
|
|
|
|
available_token_condvar: Arc<Condvar>,
|
2022-09-02 09:34:37 +00:00
|
|
|
finished: bool,
|
2022-08-23 15:44:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl ConcurrencyLimiter {
|
|
|
|
pub(super) fn new(sess: &Session, pending_jobs: usize) -> Self {
|
|
|
|
let state = Arc::new(Mutex::new(state::ConcurrencyLimiterState::new(pending_jobs)));
|
|
|
|
let available_token_condvar = Arc::new(Condvar::new());
|
|
|
|
|
|
|
|
let state_helper = state.clone();
|
|
|
|
let available_token_condvar_helper = available_token_condvar.clone();
|
|
|
|
let helper_thread = sess
|
|
|
|
.jobserver
|
|
|
|
.clone()
|
|
|
|
.into_helper_thread(move |token| {
|
|
|
|
let mut state = state_helper.lock().unwrap();
|
2023-04-09 12:58:45 +00:00
|
|
|
match token {
|
|
|
|
Ok(token) => {
|
|
|
|
state.add_new_token(token);
|
|
|
|
available_token_condvar_helper.notify_one();
|
|
|
|
}
|
|
|
|
Err(err) => {
|
|
|
|
state.poison(format!("failed to acquire jobserver token: {}", err));
|
|
|
|
// Notify all threads waiting for a token to give them a chance to
|
|
|
|
// gracefully exit.
|
|
|
|
available_token_condvar_helper.notify_all();
|
|
|
|
}
|
|
|
|
}
|
2022-08-23 15:44:39 +00:00
|
|
|
})
|
|
|
|
.unwrap();
|
|
|
|
ConcurrencyLimiter {
|
|
|
|
helper_thread: Some(helper_thread),
|
|
|
|
state,
|
2023-02-10 18:08:39 +00:00
|
|
|
available_token_condvar,
|
2022-09-02 09:34:37 +00:00
|
|
|
finished: false,
|
2022-08-23 15:44:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-09 12:58:45 +00:00
|
|
|
pub(super) fn acquire(&mut self, handler: &rustc_errors::Handler) -> ConcurrencyLimiterToken {
|
2022-08-23 15:44:39 +00:00
|
|
|
let mut state = self.state.lock().unwrap();
|
|
|
|
loop {
|
|
|
|
state.assert_invariants();
|
|
|
|
|
2023-04-09 12:58:45 +00:00
|
|
|
match state.try_start_job() {
|
|
|
|
Ok(true) => {
|
|
|
|
return ConcurrencyLimiterToken {
|
|
|
|
state: self.state.clone(),
|
|
|
|
available_token_condvar: self.available_token_condvar.clone(),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
Ok(false) => {}
|
|
|
|
Err(err) => {
|
|
|
|
// An error happened when acquiring the token. Raise it as fatal error.
|
|
|
|
// Make sure to drop the mutex guard first to prevent poisoning the mutex.
|
|
|
|
drop(state);
|
|
|
|
if let Some(err) = err {
|
Restrict `From<S>` for `{D,Subd}iagnosticMessage`.
Currently a `{D,Subd}iagnosticMessage` can be created from any type that
impls `Into<String>`. That includes `&str`, `String`, and `Cow<'static,
str>`, which are reasonable. It also includes `&String`, which is pretty
weird, and results in many places making unnecessary allocations for
patterns like this:
```
self.fatal(&format!(...))
```
This creates a string with `format!`, takes a reference, passes the
reference to `fatal`, which does an `into()`, which clones the
reference, doing a second allocation. Two allocations for a single
string, bleh.
This commit changes the `From` impls so that you can only create a
`{D,Subd}iagnosticMessage` from `&str`, `String`, or `Cow<'static,
str>`. This requires changing all the places that currently create one
from a `&String`. Most of these are of the `&format!(...)` form
described above; each one removes an unnecessary static `&`, plus an
allocation when executed. There are also a few places where the existing
use of `&String` was more reasonable; these now just use `clone()` at
the call site.
As well as making the code nicer and more efficient, this is a step
towards possibly using `Cow<'static, str>` in
`{D,Subd}iagnosticMessage::{Str,Eager}`. That would require changing
the `From<&'a str>` impls to `From<&'static str>`, which is doable, but
I'm not yet sure if it's worthwhile.
2023-04-20 13:26:58 +10:00
|
|
|
handler.fatal(err).raise();
|
2023-04-09 12:58:45 +00:00
|
|
|
} else {
|
|
|
|
// The error was already emitted, but compilation continued. Raise a silent
|
|
|
|
// fatal error.
|
|
|
|
rustc_errors::FatalError.raise();
|
|
|
|
}
|
|
|
|
}
|
2022-08-23 15:44:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
self.helper_thread.as_mut().unwrap().request_token();
|
|
|
|
state = self.available_token_condvar.wait(state).unwrap();
|
|
|
|
}
|
|
|
|
}
|
2022-08-23 16:51:06 +00:00
|
|
|
|
|
|
|
pub(super) fn job_already_done(&mut self) {
|
|
|
|
let mut state = self.state.lock().unwrap();
|
|
|
|
state.job_already_done();
|
|
|
|
}
|
2022-08-23 15:44:39 +00:00
|
|
|
|
2022-09-02 09:34:37 +00:00
|
|
|
pub(crate) fn finished(mut self) {
|
2022-08-23 15:44:39 +00:00
|
|
|
self.helper_thread.take();
|
|
|
|
|
|
|
|
// Assert that all jobs have finished
|
|
|
|
let state = Mutex::get_mut(Arc::get_mut(&mut self.state).unwrap()).unwrap();
|
|
|
|
state.assert_done();
|
2022-09-02 09:34:37 +00:00
|
|
|
|
|
|
|
self.finished = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Drop for ConcurrencyLimiter {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
if !self.finished && !std::thread::panicking() {
|
|
|
|
panic!("Forgot to call finished() on ConcurrencyLimiter");
|
|
|
|
}
|
2022-08-23 15:44:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub(super) struct ConcurrencyLimiterToken {
|
|
|
|
state: Arc<Mutex<state::ConcurrencyLimiterState>>,
|
|
|
|
available_token_condvar: Arc<Condvar>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Drop for ConcurrencyLimiterToken {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
let mut state = self.state.lock().unwrap();
|
|
|
|
state.job_finished();
|
|
|
|
self.available_token_condvar.notify_one();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
mod state {
|
|
|
|
use jobserver::Acquired;
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub(super) struct ConcurrencyLimiterState {
|
|
|
|
pending_jobs: usize,
|
|
|
|
active_jobs: usize,
|
|
|
|
|
2023-04-09 12:58:45 +00:00
|
|
|
poisoned: bool,
|
|
|
|
stored_error: Option<String>,
|
|
|
|
|
2022-08-23 15:44:39 +00:00
|
|
|
// None is used to represent the implicit token, Some to represent explicit tokens
|
|
|
|
tokens: Vec<Option<Acquired>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ConcurrencyLimiterState {
|
|
|
|
pub(super) fn new(pending_jobs: usize) -> Self {
|
2023-04-09 12:58:45 +00:00
|
|
|
ConcurrencyLimiterState {
|
|
|
|
pending_jobs,
|
|
|
|
active_jobs: 0,
|
|
|
|
poisoned: false,
|
|
|
|
stored_error: None,
|
|
|
|
tokens: vec![None],
|
|
|
|
}
|
2022-08-23 15:44:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub(super) fn assert_invariants(&self) {
|
|
|
|
// There must be no excess active jobs
|
|
|
|
assert!(self.active_jobs <= self.pending_jobs);
|
|
|
|
|
|
|
|
// There may not be more active jobs than there are tokens
|
|
|
|
assert!(self.active_jobs <= self.tokens.len());
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(super) fn assert_done(&self) {
|
|
|
|
assert_eq!(self.pending_jobs, 0);
|
|
|
|
assert_eq!(self.active_jobs, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(super) fn add_new_token(&mut self, token: Acquired) {
|
|
|
|
self.tokens.push(Some(token));
|
|
|
|
self.drop_excess_capacity();
|
|
|
|
}
|
|
|
|
|
2023-04-09 12:58:45 +00:00
|
|
|
pub(super) fn try_start_job(&mut self) -> Result<bool, Option<String>> {
|
|
|
|
if self.poisoned {
|
|
|
|
return Err(self.stored_error.take());
|
|
|
|
}
|
|
|
|
|
2022-08-23 15:44:39 +00:00
|
|
|
if self.active_jobs < self.tokens.len() {
|
|
|
|
// Using existing token
|
|
|
|
self.job_started();
|
2023-04-09 12:58:45 +00:00
|
|
|
return Ok(true);
|
2022-08-23 15:44:39 +00:00
|
|
|
}
|
|
|
|
|
2023-04-09 12:58:45 +00:00
|
|
|
Ok(false)
|
2022-08-23 15:44:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub(super) fn job_started(&mut self) {
|
|
|
|
self.assert_invariants();
|
|
|
|
self.active_jobs += 1;
|
|
|
|
self.drop_excess_capacity();
|
|
|
|
self.assert_invariants();
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(super) fn job_finished(&mut self) {
|
|
|
|
self.assert_invariants();
|
|
|
|
self.pending_jobs -= 1;
|
|
|
|
self.active_jobs -= 1;
|
|
|
|
self.assert_invariants();
|
|
|
|
self.drop_excess_capacity();
|
|
|
|
self.assert_invariants();
|
|
|
|
}
|
|
|
|
|
2022-08-23 16:51:06 +00:00
|
|
|
pub(super) fn job_already_done(&mut self) {
|
|
|
|
self.assert_invariants();
|
|
|
|
self.pending_jobs -= 1;
|
|
|
|
self.assert_invariants();
|
|
|
|
self.drop_excess_capacity();
|
|
|
|
self.assert_invariants();
|
|
|
|
}
|
|
|
|
|
2023-04-09 12:58:45 +00:00
|
|
|
pub(super) fn poison(&mut self, error: String) {
|
|
|
|
self.poisoned = true;
|
|
|
|
self.stored_error = Some(error);
|
|
|
|
}
|
|
|
|
|
2022-08-23 15:44:39 +00:00
|
|
|
fn drop_excess_capacity(&mut self) {
|
|
|
|
self.assert_invariants();
|
2022-08-24 12:22:01 +02:00
|
|
|
|
|
|
|
// Drop all tokens that can never be used anymore
|
|
|
|
self.tokens.truncate(std::cmp::max(self.pending_jobs, 1));
|
|
|
|
|
|
|
|
// Keep some excess tokens to satisfy requests faster
|
|
|
|
const MAX_EXTRA_CAPACITY: usize = 2;
|
|
|
|
self.tokens.truncate(std::cmp::max(self.active_jobs + MAX_EXTRA_CAPACITY, 1));
|
|
|
|
|
2022-08-23 15:44:39 +00:00
|
|
|
self.assert_invariants();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|