Skip to content

Commit f93afb9

Browse files
committed
feat: parallelize dependency fetching and installs with rayon
Both the resolver and installer now run their per-dependency work in parallel via rayon::par_iter, so N module clones, N plugin asset downloads, and N module exports proceed concurrently instead of serially. Supporting changes: - git::clone_or_fetch takes a per-URL mutex so concurrent callers for the same repo serialize rather than corrupting the on-disk cache. - ui::LOG_CAPTURE moved from thread_local to a global Mutex so the TUI captures log lines emitted from rayon worker threads. - Added a global mutex around the cargo-fallback stdin prompt and an AtomicU64 counter for staging dir names so concurrent installs can't collide on a nanosecond timestamp.
1 parent f50b93c commit f93afb9

6 files changed

Lines changed: 515 additions & 326 deletions

File tree

Cargo.lock

Lines changed: 46 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ ratatui = "0.29"
2525
crossterm = "0.29"
2626
syntect = "5.2"
2727
tui-scrollview = "0.5"
28+
rayon = "1"
2829

2930
[dependencies.nu-protocol]
3031
version = "0.112.2"

src/git.rs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
use std::collections::HashSet;
1+
use std::collections::{HashMap, HashSet};
22
use std::path::{Path, PathBuf};
3-
use std::sync::{Mutex, OnceLock};
3+
use std::sync::{Arc, Mutex, OnceLock};
44

55
use git2::{FetchOptions, Progress, RemoteCallbacks, Repository, build::RepoBuilder};
66
use indicatif::ProgressBar;
@@ -11,6 +11,21 @@ use crate::error::{QuiverError, Result};
1111
use crate::ui;
1212

1313
static FETCHED_THIS_RUN: OnceLock<Mutex<HashSet<String>>> = OnceLock::new();
14+
static URL_LOCKS: OnceLock<Mutex<HashMap<String, Arc<Mutex<()>>>>> = OnceLock::new();
15+
16+
fn url_locks() -> &'static Mutex<HashMap<String, Arc<Mutex<()>>>> {
17+
URL_LOCKS.get_or_init(|| Mutex::new(HashMap::new()))
18+
}
19+
20+
/// Acquire a per-URL serialization lock so concurrent callers don't race on the
21+
/// same cached repo. Returns an owned guard that releases on drop.
22+
fn lock_for_url(url: &str) -> Arc<Mutex<()>> {
23+
let key = normalize_git_url(url);
24+
let mut map = url_locks().lock().expect("url lock map poisoned");
25+
map.entry(key)
26+
.or_insert_with(|| Arc::new(Mutex::new(())))
27+
.clone()
28+
}
1429

1530
/// Returns the global install directory for git repos:
1631
/// `~/.local/share/quiver/installs/git/` on macOS/Linux.
@@ -73,6 +88,11 @@ pub fn clone_or_fetch(url: &str) -> Result<PathBuf> {
7388
let repo_dir = cache.join(url_to_dirname(url));
7489
let repo_label = repo_name_from_url(url).unwrap_or_else(|| url.to_string());
7590

91+
// Serialize concurrent callers for the same URL — git2 operations against a
92+
// shared on-disk repo are not safe to overlap.
93+
let url_lock = lock_for_url(url);
94+
let _url_guard = url_lock.lock().expect("url lock poisoned");
95+
7696
if repo_dir.exists() {
7797
if was_fetched_this_run(url) {
7898
return Ok(repo_dir);

0 commit comments

Comments
 (0)