Skip to content

Commit 41405f1

Browse files
Improve concurrency control for cargo_tree_resolver (#3580)
Not spawning everything at once makes sense, but doing it in batches per-host can lead to some bottlenecking as well as CPU contention if there are many platforms. Use a "semaphore" instead. Co-authored-by: Daniel Wagner-Hall <[email protected]>
1 parent 42776e3 commit 41405f1

File tree

1 file changed

+112
-68
lines changed

1 file changed

+112
-68
lines changed

crate_universe/src/metadata/cargo_tree_resolver.rs

Lines changed: 112 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
use std::collections::{BTreeMap, BTreeSet, HashMap};
44
use std::io::BufRead;
55
use std::path::{Path, PathBuf};
6-
use std::process::Child;
6+
use std::process::Output;
7+
use std::thread;
78

89
use anyhow::{anyhow, bail, Context, Result};
910
use camino::Utf8Path;
@@ -131,84 +132,127 @@ impl TreeResolver {
131132
.insert(triple);
132133
}
133134

134-
for host_triple in cargo_host_triples.keys() {
135-
// Note that for each host triple `cargo tree` child processes are spawned and then
136-
// immediately waited upon so that we don't end up with `{HOST_TRIPLES} * {TARGET_TRIPLES}`
137-
// number of processes (which can be +400 and hit operating system limitations).
138-
let mut target_triple_to_child = BTreeMap::<String, Child>::new();
135+
// Limit the concurrency so we don't concurrently spawn `{HOST_TRIPLES} * {TARGET_TRIPLES}`
136+
// number of processes (which can be +400 and hit operating system limitations).
137+
let max_parallel = thread::available_parallelism()
138+
.map(|n| n.get())
139+
.unwrap_or(4);
140+
141+
// Prepare all unique jobs: (cargo_host, cargo_target)
142+
let mut jobs = Vec::<(String, String)>::new();
143+
for cargo_host in cargo_host_triples.keys() {
144+
for cargo_target in cargo_target_triples.keys() {
145+
jobs.push((cargo_host.clone(), cargo_target.clone()));
146+
}
147+
}
148+
149+
// Spawn workers up to the cap; join one whenever the cap is reached.
150+
let mut in_flight =
151+
Vec::<thread::JoinHandle<anyhow::Result<(String, String, Output)>>>::new();
152+
let mut results = Vec::<(String, String, Output)>::new();
153+
154+
for (cargo_host, cargo_target) in jobs {
155+
// If we've hit the limit, free a slot by joining one worker.
156+
if in_flight.len() >= max_parallel {
157+
let res = in_flight
158+
.remove(0)
159+
.join()
160+
.expect("worker thread panicked")?;
161+
results.push(res);
162+
}
139163

140164
debug!(
141-
"Spawning `cargo tree` processes for host `{}`: {}",
142-
host_triple,
143-
cargo_target_triples.keys().len(),
165+
"Spawning `cargo tree` process for host `{}`: {}",
166+
cargo_host, cargo_target,
144167
);
145168

146-
for target_triple in cargo_target_triples.keys() {
147-
// We use `cargo tree` here because `cargo metadata` doesn't report
148-
// back target-specific features (enabled with `resolver = "2"`).
149-
// This is unfortunately a bit of a hack. See:
150-
// - https://github.com/rust-lang/cargo/issues/9863
151-
// - https://github.com/bazelbuild/rules_rust/issues/1662
152-
let child = self
153-
.cargo_bin
154-
.command()?
155-
// These next two environment variables are used to hack cargo into using a custom
156-
// host triple instead of the host triple detected by rustc.
157-
.env("RUSTC_WRAPPER", rustc_wrapper)
158-
.env("HOST_TRIPLE", host_triple)
159-
.env("CARGO_CACHE_RUSTC_INFO", "0")
160-
.current_dir(manifest_path.parent().expect("All manifests should have a valid parent."))
161-
.arg("tree")
162-
.arg("--manifest-path")
163-
.arg(manifest_path)
164-
.arg("--edges")
165-
.arg("normal,build,dev")
166-
.arg("--prefix=indent")
167-
// https://doc.rust-lang.org/cargo/commands/cargo-tree.html#tree-formatting-options
168-
.arg("--format=;{p};{f};")
169-
.arg("--color=never")
170-
.arg("--charset=ascii")
171-
.arg("--workspace")
172-
.arg("--target")
173-
.arg(target_triple)
174-
.stdout(std::process::Stdio::piped())
175-
.stderr(std::process::Stdio::piped())
176-
.spawn()
177-
.with_context(|| {
178-
format!(
169+
let manifest_path = manifest_path.to_owned();
170+
let rustc_wrapper = rustc_wrapper.to_owned();
171+
let cargo_bin = self.cargo_bin.clone();
172+
173+
in_flight.push(thread::spawn(
174+
move || -> anyhow::Result<(String, String, Output)> {
175+
// We use `cargo tree` here because `cargo metadata` doesn't report
176+
// back target-specific features (enabled with `resolver = "2"`).
177+
// This is unfortunately a bit of a hack. See:
178+
// - https://github.com/rust-lang/cargo/issues/9863
179+
// - https://github.com/bazelbuild/rules_rust/issues/1662
180+
let child = cargo_bin
181+
.command()?
182+
// These next two environment variables are used to hack cargo into using a custom
183+
// host triple instead of the host triple detected by rustc.
184+
.env("RUSTC_WRAPPER", &rustc_wrapper)
185+
.env("HOST_TRIPLE", &cargo_host)
186+
.env("CARGO_CACHE_RUSTC_INFO", "0")
187+
.current_dir(
188+
manifest_path
189+
.parent()
190+
.expect("All manifests should have a valid parent."),
191+
)
192+
.arg("tree")
193+
.arg("--manifest-path")
194+
.arg(&manifest_path)
195+
.arg("--edges")
196+
.arg("normal,build,dev")
197+
.arg("--prefix=indent")
198+
// https://doc.rust-lang.org/cargo/commands/cargo-tree.html#tree-formatting-options
199+
.arg("--format=;{p};{f};")
200+
.arg("--color=never")
201+
.arg("--charset=ascii")
202+
.arg("--workspace")
203+
.arg("--target")
204+
.arg(&cargo_target)
205+
.stdout(std::process::Stdio::piped())
206+
.stderr(std::process::Stdio::piped())
207+
.spawn()
208+
.with_context(|| {
209+
format!(
179210
"Error spawning cargo in child process to compute features for target '{}', manifest path '{}'",
180-
target_triple,
211+
cargo_target,
212+
manifest_path.display()
213+
)
214+
})?;
215+
216+
let output = child.wait_with_output().with_context(|| {
217+
format!(
218+
"Error running `cargo tree --target={}` (host = '{}'), manifest path '{}'",
219+
cargo_target,
220+
cargo_host,
181221
manifest_path.display()
182222
)
183223
})?;
184-
target_triple_to_child.insert(target_triple.clone(), child);
185-
}
186224

187-
for (target_triple, child) in target_triple_to_child.into_iter() {
188-
let output = child.wait_with_output().with_context(|| {
189-
format!(
190-
"Error running `cargo tree --target={}` (host = '{}'), manifest path '{}'",
191-
target_triple,
192-
host_triple,
193-
manifest_path.display()
194-
)
195-
})?;
196-
if !output.status.success() {
197-
tracing::error!("{}", String::from_utf8_lossy(&output.stdout));
198-
tracing::error!("{}", String::from_utf8_lossy(&output.stderr));
199-
bail!(format!("Failed to run cargo tree: {}", output.status))
200-
}
225+
Ok((cargo_host, cargo_target, output))
226+
},
227+
));
228+
}
229+
230+
for handle in in_flight {
231+
let res = handle.join().expect("worker thread panicked")?;
232+
results.push(res);
233+
}
201234

202-
tracing::trace!("`cargo tree --target={}` completed.", target_triple);
235+
// Process results and replicate outputs for de-duplicated platforms.
236+
for (cargo_host, cargo_target, output) in results {
237+
if !output.status.success() {
238+
tracing::error!("{}", String::from_utf8_lossy(&output.stdout));
239+
tracing::error!("{}", String::from_utf8_lossy(&output.stderr));
240+
bail!(format!("Failed to run cargo tree: {}", output.status));
241+
}
203242

204-
// Replicate outputs for any de-duplicated platforms
205-
for host_plat in cargo_host_triples[host_triple].iter() {
206-
for target_plat in cargo_target_triples[&target_triple].iter() {
207-
stdouts
208-
.entry((*host_plat).clone())
209-
.or_default()
210-
.insert((*target_plat).clone(), output.stdout.clone());
211-
}
243+
tracing::trace!(
244+
"`cargo tree --target={}` (host `{}`) completed.",
245+
cargo_target,
246+
cargo_host
247+
);
248+
249+
// Replicate outputs for any de-duplicated platforms
250+
for host_plat in cargo_host_triples[&cargo_host].iter() {
251+
for target_plat in cargo_target_triples[&cargo_target].iter() {
252+
stdouts
253+
.entry((*host_plat).clone())
254+
.or_default()
255+
.insert((*target_plat).clone(), output.stdout.clone());
212256
}
213257
}
214258
}

0 commit comments

Comments
 (0)