switch to using a make job server for managing test parallelism
Some checks failed
/ test (push) Failing after 39m16s
Some checks failed
/ test (push) Failing after 39m16s
This commit is contained in:
parent
3d0f95cfe5
commit
42effd1132
60
Cargo.lock
generated
60
Cargo.lock
generated
|
@ -230,6 +230,27 @@ dependencies = [
|
||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ctor"
|
||||||
|
version = "0.2.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive_destructure2"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "64b697ac90ff296f0fc031ee5a61c7ac31fb9fff50e3fb32873b09223613fc0c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "digest"
|
name = "digest"
|
||||||
version = "0.10.7"
|
version = "0.10.7"
|
||||||
|
@ -285,10 +306,12 @@ dependencies = [
|
||||||
"bitvec",
|
"bitvec",
|
||||||
"blake3",
|
"blake3",
|
||||||
"clap",
|
"clap",
|
||||||
|
"ctor",
|
||||||
"eyre",
|
"eyre",
|
||||||
"fayalite-proc-macros",
|
"fayalite-proc-macros",
|
||||||
"fayalite-visit-gen",
|
"fayalite-visit-gen",
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
|
"jobslot",
|
||||||
"num-bigint",
|
"num-bigint",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"os_pipe",
|
"os_pipe",
|
||||||
|
@ -350,6 +373,17 @@ dependencies = [
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glob"
|
name = "glob"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
|
@ -410,6 +444,20 @@ version = "1.0.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
|
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jobslot"
|
||||||
|
version = "0.2.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fe10868679d7a24c2c67d862d0e64a342ce9aef7cdde9ce8019bd35d353d458d"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"derive_destructure2",
|
||||||
|
"getrandom",
|
||||||
|
"libc",
|
||||||
|
"scopeguard",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.153"
|
version = "0.2.153"
|
||||||
|
@ -520,6 +568,12 @@ version = "1.0.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
|
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scopeguard"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.202"
|
version = "1.0.202"
|
||||||
|
@ -672,6 +726,12 @@ version = "0.9.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "which"
|
name = "which"
|
||||||
version = "6.0.1"
|
version = "6.0.1"
|
||||||
|
|
|
@ -21,9 +21,11 @@ base16ct = "0.2.0"
|
||||||
bitvec = { version = "1.0.1", features = ["serde"] }
|
bitvec = { version = "1.0.1", features = ["serde"] }
|
||||||
blake3 = { version = "1.5.4", features = ["serde"] }
|
blake3 = { version = "1.5.4", features = ["serde"] }
|
||||||
clap = { version = "4.5.9", features = ["derive", "env", "string"] }
|
clap = { version = "4.5.9", features = ["derive", "env", "string"] }
|
||||||
|
ctor = "0.2.8"
|
||||||
eyre = "0.6.12"
|
eyre = "0.6.12"
|
||||||
hashbrown = "0.14.3"
|
hashbrown = "0.14.3"
|
||||||
indexmap = { version = "2.2.6", features = ["serde"] }
|
indexmap = { version = "2.2.6", features = ["serde"] }
|
||||||
|
jobslot = "0.2.19"
|
||||||
num-bigint = "0.4.4"
|
num-bigint = "0.4.4"
|
||||||
num-traits = "0.2.16"
|
num-traits = "0.2.16"
|
||||||
os_pipe = "1.2.1"
|
os_pipe = "1.2.1"
|
||||||
|
|
|
@ -17,9 +17,11 @@ version.workspace = true
|
||||||
bitvec.workspace = true
|
bitvec.workspace = true
|
||||||
blake3.workspace = true
|
blake3.workspace = true
|
||||||
clap.workspace = true
|
clap.workspace = true
|
||||||
|
ctor.workspace = true
|
||||||
eyre.workspace = true
|
eyre.workspace = true
|
||||||
fayalite-proc-macros.workspace = true
|
fayalite-proc-macros.workspace = true
|
||||||
hashbrown.workspace = true
|
hashbrown.workspace = true
|
||||||
|
jobslot.workspace = true
|
||||||
num-bigint.workspace = true
|
num-bigint.workspace = true
|
||||||
num-traits.workspace = true
|
num-traits.workspace = true
|
||||||
os_pipe.workspace = true
|
os_pipe.workspace = true
|
||||||
|
|
|
@ -5,7 +5,7 @@ use crate::{
|
||||||
firrtl::{self, ExportOptions},
|
firrtl::{self, ExportOptions},
|
||||||
intern::Interned,
|
intern::Interned,
|
||||||
module::Module,
|
module::Module,
|
||||||
util::streaming_read_utf8::streaming_read_utf8,
|
util::{job_server::AcquiredJob, streaming_read_utf8::streaming_read_utf8},
|
||||||
};
|
};
|
||||||
use clap::{
|
use clap::{
|
||||||
builder::{OsStringValueParser, TypedValueParser},
|
builder::{OsStringValueParser, TypedValueParser},
|
||||||
|
@ -18,10 +18,8 @@ use std::{
|
||||||
ffi::OsString,
|
ffi::OsString,
|
||||||
fmt::{self, Write},
|
fmt::{self, Write},
|
||||||
fs, io, mem,
|
fs, io, mem,
|
||||||
num::NonZeroUsize,
|
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
process,
|
process,
|
||||||
sync::{Condvar, Mutex, OnceLock},
|
|
||||||
};
|
};
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
|
||||||
|
@ -49,107 +47,10 @@ impl From<io::Error> for CliError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct AcquiredJob {
|
|
||||||
job_count: NonZeroUsize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for AcquiredJob {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
Self::change_job_count(Some(self.job_count), None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AcquiredJob {
|
|
||||||
pub fn max_available_job_count() -> NonZeroUsize {
|
|
||||||
static RETVAL: OnceLock<NonZeroUsize> = OnceLock::new();
|
|
||||||
*RETVAL.get_or_init(|| {
|
|
||||||
std::thread::available_parallelism().unwrap_or(NonZeroUsize::new(1).unwrap())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
fn change_job_count(released: Option<NonZeroUsize>, acquired: Option<NonZeroUsize>) {
|
|
||||||
static AVAILABLE_JOB_COUNT: OnceLock<Mutex<usize>> = OnceLock::new();
|
|
||||||
static COND_VAR: Condvar = Condvar::new();
|
|
||||||
let mut available_job_count_lock = AVAILABLE_JOB_COUNT
|
|
||||||
.get_or_init(|| Mutex::new(Self::max_available_job_count().get()))
|
|
||||||
.lock()
|
|
||||||
.unwrap();
|
|
||||||
if let Some(released) = released {
|
|
||||||
*available_job_count_lock = available_job_count_lock
|
|
||||||
.checked_add(released.get())
|
|
||||||
.expect("tried to release too many jobs");
|
|
||||||
COND_VAR.notify_all();
|
|
||||||
}
|
|
||||||
if let Some(acquired) = acquired {
|
|
||||||
loop {
|
|
||||||
match available_job_count_lock.checked_sub(acquired.get()) {
|
|
||||||
Some(jobs_left) => {
|
|
||||||
*available_job_count_lock = jobs_left;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
available_job_count_lock = COND_VAR.wait(available_job_count_lock).unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn job_count(&self) -> NonZeroUsize {
|
|
||||||
self.job_count
|
|
||||||
}
|
|
||||||
pub fn increase_job_count<R>(
|
|
||||||
&mut self,
|
|
||||||
new_minimum_count: NonZeroUsize,
|
|
||||||
f: impl FnOnce(&mut AcquiredJob) -> R,
|
|
||||||
) -> R {
|
|
||||||
if new_minimum_count <= self.job_count {
|
|
||||||
return f(self);
|
|
||||||
}
|
|
||||||
struct ReleaseOnDrop<'a> {
|
|
||||||
acquired_job: &'a mut AcquiredJob,
|
|
||||||
old_job_count: NonZeroUsize,
|
|
||||||
}
|
|
||||||
impl Drop for ReleaseOnDrop<'_> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
AcquiredJob::change_job_count(
|
|
||||||
NonZeroUsize::new(self.acquired_job.job_count.get() - self.old_job_count.get()),
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
self.acquired_job.job_count = self.old_job_count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let release_on_drop = ReleaseOnDrop {
|
|
||||||
old_job_count: self.job_count,
|
|
||||||
acquired_job: self,
|
|
||||||
};
|
|
||||||
// release our current jobs when acquiring new jobs to avoid deadlock
|
|
||||||
Self::change_job_count(Some(release_on_drop.old_job_count), Some(new_minimum_count));
|
|
||||||
release_on_drop.acquired_job.job_count = new_minimum_count;
|
|
||||||
let retval = f(release_on_drop.acquired_job);
|
|
||||||
drop(release_on_drop);
|
|
||||||
retval
|
|
||||||
}
|
|
||||||
pub fn acquire_jobs(job_count: NonZeroUsize) -> Self {
|
|
||||||
Self::change_job_count(None, Some(job_count));
|
|
||||||
Self { job_count }
|
|
||||||
}
|
|
||||||
pub fn run_command<R>(
|
|
||||||
&mut self,
|
|
||||||
mut cmd: std::process::Command,
|
|
||||||
f: impl FnOnce(&mut std::process::Command) -> std::io::Result<R>,
|
|
||||||
) -> std::io::Result<R> {
|
|
||||||
// TODO: if we implement a make job server, add the proper env vars to cmd
|
|
||||||
f(&mut cmd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait RunPhase<Arg> {
|
pub trait RunPhase<Arg> {
|
||||||
type Output;
|
type Output;
|
||||||
fn run(&self, arg: Arg) -> Result<Self::Output> {
|
fn run(&self, arg: Arg) -> Result<Self::Output> {
|
||||||
self.run_with_job(
|
self.run_with_job(arg, &mut AcquiredJob::acquire())
|
||||||
arg,
|
|
||||||
&mut AcquiredJob::acquire_jobs(NonZeroUsize::new(1).unwrap()),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
fn run_with_job(&self, arg: Arg, acquired_job: &mut AcquiredJob) -> Result<Self::Output>;
|
fn run_with_job(&self, arg: Arg, acquired_job: &mut AcquiredJob) -> Result<Self::Output>;
|
||||||
}
|
}
|
||||||
|
@ -492,14 +393,6 @@ impl FormalMode {
|
||||||
FormalMode::Cover => "cover",
|
FormalMode::Cover => "cover",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn needs_extra_job(self) -> bool {
|
|
||||||
match self {
|
|
||||||
FormalMode::BMC => false,
|
|
||||||
FormalMode::Prove => true,
|
|
||||||
FormalMode::Live => false,
|
|
||||||
FormalMode::Cover => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for FormalMode {
|
impl fmt::Display for FormalMode {
|
||||||
|
@ -721,12 +614,6 @@ impl FormalArgs {
|
||||||
cmd.arg(sby_file.file_name().unwrap());
|
cmd.arg(sby_file.file_name().unwrap());
|
||||||
cmd.args(&self.sby_extra_args);
|
cmd.args(&self.sby_extra_args);
|
||||||
cmd.current_dir(&output.verilog.firrtl.output_dir);
|
cmd.current_dir(&output.verilog.firrtl.output_dir);
|
||||||
let new_minimum_count = if self.mode.needs_extra_job() {
|
|
||||||
NonZeroUsize::new(2).unwrap()
|
|
||||||
} else {
|
|
||||||
NonZeroUsize::new(1).unwrap()
|
|
||||||
};
|
|
||||||
let new_minimum_count = AcquiredJob::max_available_job_count().min(new_minimum_count);
|
|
||||||
let mut captured_output = String::new();
|
let mut captured_output = String::new();
|
||||||
let cache_file = output.cache_file();
|
let cache_file = output.cache_file();
|
||||||
let do_cache = if let Some(contents_hash) = contents_hash.filter(|_| self.cache_results) {
|
let do_cache = if let Some(contents_hash) = contents_hash.filter(|_| self.cache_results) {
|
||||||
|
@ -752,13 +639,11 @@ impl FormalArgs {
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
let _ = fs::remove_file(&cache_file);
|
let _ = fs::remove_file(&cache_file);
|
||||||
let status = acquired_job.increase_job_count(new_minimum_count, |acquired_job| {
|
let status = self.verilog.firrtl.base.run_external_command(
|
||||||
self.verilog.firrtl.base.run_external_command(
|
|
||||||
acquired_job,
|
acquired_job,
|
||||||
cmd,
|
cmd,
|
||||||
do_cache.then_some(&mut captured_output),
|
do_cache.then_some(&mut captured_output),
|
||||||
)
|
)?;
|
||||||
})?;
|
|
||||||
let result = if status.success() {
|
let result = if status.success() {
|
||||||
Ok(output)
|
Ok(output)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -27,4 +27,5 @@ pub use misc::{
|
||||||
interned_bit, iter_eq_by, BitSliceWriteWithBase, DebugAsDisplay, DebugAsRawString, MakeMutSlice,
|
interned_bit, iter_eq_by, BitSliceWriteWithBase, DebugAsDisplay, DebugAsRawString, MakeMutSlice,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub mod job_server;
|
||||||
pub mod ready_valid;
|
pub mod ready_valid;
|
||||||
|
|
193
crates/fayalite/src/util/job_server.rs
Normal file
193
crates/fayalite/src/util/job_server.rs
Normal file
|
@ -0,0 +1,193 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
|
|
||||||
|
use ctor::ctor;
|
||||||
|
use jobslot::{Acquired, Client};
|
||||||
|
use std::{
|
||||||
|
ffi::OsString,
|
||||||
|
mem,
|
||||||
|
num::NonZeroUsize,
|
||||||
|
sync::{Condvar, Mutex, Once, OnceLock},
|
||||||
|
thread::spawn,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn get_or_make_client() -> &'static Client {
|
||||||
|
#[ctor]
|
||||||
|
static CLIENT: OnceLock<Client> = unsafe {
|
||||||
|
match Client::from_env() {
|
||||||
|
Some(client) => OnceLock::from(client),
|
||||||
|
None => OnceLock::new(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
CLIENT.get_or_init(|| {
|
||||||
|
let mut available_parallelism = None;
|
||||||
|
let mut args = std::env::args_os().skip(1);
|
||||||
|
while let Some(arg) = args.next() {
|
||||||
|
const TEST_THREADS_OPTION: &'static [u8] = b"--test-threads";
|
||||||
|
if arg.as_encoded_bytes().starts_with(TEST_THREADS_OPTION) {
|
||||||
|
match arg.as_encoded_bytes().get(TEST_THREADS_OPTION.len()) {
|
||||||
|
Some(b'=') => {
|
||||||
|
let mut arg = arg.into_encoded_bytes();
|
||||||
|
arg.drain(..=TEST_THREADS_OPTION.len());
|
||||||
|
available_parallelism = Some(arg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
available_parallelism = args.next().map(OsString::into_encoded_bytes);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let available_parallelism = if let Some(available_parallelism) = available_parallelism
|
||||||
|
.as_deref()
|
||||||
|
.and_then(|v| std::str::from_utf8(v).ok())
|
||||||
|
.and_then(|v| v.parse().ok())
|
||||||
|
{
|
||||||
|
available_parallelism
|
||||||
|
} else if let Ok(available_parallelism) = std::thread::available_parallelism() {
|
||||||
|
available_parallelism
|
||||||
|
} else {
|
||||||
|
NonZeroUsize::new(1).unwrap()
|
||||||
|
};
|
||||||
|
Client::new_with_fifo(available_parallelism.get() - 1).expect("failed to create job server")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
waiting_count: usize,
|
||||||
|
available: Vec<Acquired>,
|
||||||
|
implicit_available: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
fn total_available(&self) -> usize {
|
||||||
|
self.available.len() + self.implicit_available as usize
|
||||||
|
}
|
||||||
|
fn additional_waiting(&self) -> usize {
|
||||||
|
self.waiting_count.saturating_sub(self.total_available())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static STATE: Mutex<State> = Mutex::new(State {
|
||||||
|
waiting_count: 0,
|
||||||
|
available: Vec::new(),
|
||||||
|
implicit_available: true,
|
||||||
|
});
|
||||||
|
static COND_VAR: Condvar = Condvar::new();
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum AcquiredJobInner {
|
||||||
|
FromJobServer(Acquired),
|
||||||
|
ImplicitJob,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct AcquiredJob {
|
||||||
|
job: AcquiredJobInner,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AcquiredJob {
|
||||||
|
fn start_acquire_thread() {
|
||||||
|
static STARTED_THREAD: Once = Once::new();
|
||||||
|
STARTED_THREAD.call_once(|| {
|
||||||
|
spawn(|| {
|
||||||
|
let mut acquired = None;
|
||||||
|
let client = get_or_make_client();
|
||||||
|
let mut state = STATE.lock().unwrap();
|
||||||
|
loop {
|
||||||
|
state = if state.additional_waiting() == 0 {
|
||||||
|
if acquired.is_some() {
|
||||||
|
drop(state);
|
||||||
|
drop(acquired.take()); // drop Acquired outside of lock
|
||||||
|
STATE.lock().unwrap()
|
||||||
|
} else {
|
||||||
|
COND_VAR.wait(state).unwrap()
|
||||||
|
}
|
||||||
|
} else if acquired.is_some() {
|
||||||
|
// allocate space before moving Acquired to ensure we
|
||||||
|
// drop Acquired outside of the lock on panic
|
||||||
|
state.available.reserve(1);
|
||||||
|
state.available.push(acquired.take().unwrap());
|
||||||
|
COND_VAR.notify_all();
|
||||||
|
state
|
||||||
|
} else {
|
||||||
|
drop(state);
|
||||||
|
acquired = Some(
|
||||||
|
client
|
||||||
|
.acquire()
|
||||||
|
.expect("can't acquire token from job server"),
|
||||||
|
);
|
||||||
|
STATE.lock().unwrap()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
fn acquire_inner(block: bool) -> Option<Self> {
|
||||||
|
Self::start_acquire_thread();
|
||||||
|
let mut state = STATE.lock().unwrap();
|
||||||
|
loop {
|
||||||
|
if let Some(acquired) = state.available.pop() {
|
||||||
|
return Some(Self {
|
||||||
|
job: AcquiredJobInner::FromJobServer(acquired),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if state.implicit_available {
|
||||||
|
state.implicit_available = false;
|
||||||
|
return Some(Self {
|
||||||
|
job: AcquiredJobInner::ImplicitJob,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if !block {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
state.waiting_count += 1;
|
||||||
|
state = COND_VAR.wait(state).unwrap();
|
||||||
|
state.waiting_count -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn try_acquire() -> Option<Self> {
|
||||||
|
Self::acquire_inner(false)
|
||||||
|
}
|
||||||
|
pub fn acquire() -> Self {
|
||||||
|
Self::acquire_inner(true).expect("failed to acquire token")
|
||||||
|
}
|
||||||
|
pub fn run_command<R>(
|
||||||
|
&mut self,
|
||||||
|
cmd: std::process::Command,
|
||||||
|
f: impl FnOnce(&mut std::process::Command) -> std::io::Result<R>,
|
||||||
|
) -> std::io::Result<R> {
|
||||||
|
get_or_make_client().configure_make_and_run_with_fifo(cmd, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for AcquiredJob {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let mut state = STATE.lock().unwrap();
|
||||||
|
match &self.job {
|
||||||
|
AcquiredJobInner::FromJobServer(_) => {
|
||||||
|
if state.waiting_count > state.available.len() + state.implicit_available as usize {
|
||||||
|
// allocate space before moving Acquired to ensure we
|
||||||
|
// drop Acquired outside of the lock on panic
|
||||||
|
state.available.reserve(1);
|
||||||
|
let AcquiredJobInner::FromJobServer(acquired) =
|
||||||
|
mem::replace(&mut self.job, AcquiredJobInner::ImplicitJob)
|
||||||
|
else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
state.available.push(acquired);
|
||||||
|
COND_VAR.notify_all();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AcquiredJobInner::ImplicitJob => {
|
||||||
|
state.implicit_available = true;
|
||||||
|
if state.waiting_count > state.available.len() {
|
||||||
|
COND_VAR.notify_all();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue