diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-02-22 11:07:42 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-02-22 11:15:29 +0900 |
| commit | 9f0d210021c54f63c9984446862b6ec68834bc63 (patch) | |
| tree | d1522b8047c60bc7ee7a9d832178dd24e1b07636 /crates/mozart-registry/src/resolver.rs | |
| parent | 2c243a3cb814939bbe40fda1608781825ab0d77d (diff) | |
| download | php-mozart-9f0d210021c54f63c9984446862b6ec68834bc63.tar.gz php-mozart-9f0d210021c54f63c9984446862b6ec68834bc63.tar.zst php-mozart-9f0d210021c54f63c9984446862b6ec68834bc63.zip | |
refactor(async): migrate from blocking HTTP to async/await with tokio
Replace reqwest::blocking with async reqwest across the entire codebase.
All command execute functions, registry API calls (packagist, downloader,
resolver, lockfile), and the main entry point now use async/await with
the tokio runtime. The pubgrub resolver runs on spawn_blocking since its
DependencyProvider trait is synchronous, using Handle::block_on for
async I/O within that context.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'crates/mozart-registry/src/resolver.rs')
| -rw-r--r-- | crates/mozart-registry/src/resolver.rs | 189 |
1 files changed, 112 insertions, 77 deletions
diff --git a/crates/mozart-registry/src/resolver.rs b/crates/mozart-registry/src/resolver.rs index eb4f6e5..84e6a96 100644 --- a/crates/mozart-registry/src/resolver.rs +++ b/crates/mozart-registry/src/resolver.rs @@ -586,6 +586,9 @@ struct VersionDependencies { /// pubgrub `DependencyProvider` that fetches package metadata from Packagist. pub struct MozartProvider { + /// Tokio runtime handle for calling async functions from sync trait methods. + handle: tokio::runtime::Handle, + /// Cache of fetched package metadata. Populated lazily from Packagist. package_cache: RefCell<HashMap<String, PackageVersions>>, @@ -632,13 +635,16 @@ impl MozartProvider { } // Fetch from Packagist (with optional on-disk repo cache) - let packagist_versions = packagist::fetch_package_versions( - package_name, - self.repo_cache.as_ref(), - ) - .map_err(|e| { - ResolverError::PackagistError(format!("Failed to fetch {}: {}", package_name, e)) - })?; + // Uses block_on because pubgrub's DependencyProvider trait is synchronous. + let packagist_versions = self + .handle + .block_on(packagist::fetch_package_versions( + package_name, + self.repo_cache.as_ref(), + )) + .map_err(|e| { + ResolverError::PackagistError(format!("Failed to fetch {}: {}", package_name, e)) + })?; // Convert and filter let mut versions = BTreeMap::new(); @@ -975,8 +981,8 @@ pub struct ResolvedPackage { /// /// Returns a list of resolved packages (excluding root and platform packages), /// or a human-readable error. -pub fn resolve(request: &ResolveRequest) -> Result<Vec<ResolvedPackage>, ResolveError> { - // 1. Build root dependencies +pub async fn resolve(request: &ResolveRequest) -> Result<Vec<ResolvedPackage>, ResolveError> { + // 1. Build root dependencies (parsing is CPU-only, no async needed) let mut root_deps: Vec<(PackageName, ComposerVS)> = Vec::new(); let root_conflicts: Vec<(PackageName, ComposerVS)> = Vec::new(); @@ -1012,79 +1018,94 @@ pub fn resolve(request: &ResolveRequest) -> Result<Vec<ResolvedPackage>, Resolve } } - // 2. Build the provider - let provider = MozartProvider { - package_cache: RefCell::new(HashMap::new()), - repo_cache: request.repo_cache.clone(), - platform_packages: request.platform.to_versions(), - minimum_stability: request.minimum_stability, - stability_flags: request.stability_flags.clone(), - prefer_stable: request.prefer_stable, - prefer_lowest: request.prefer_lowest, - root_dependencies: root_deps, - root_conflicts, - ignore_platform_reqs: request.ignore_platform_reqs, - ignore_platform_req_list: request.ignore_platform_req_list.clone(), - }; + // Capture the current tokio Handle so the provider can call async functions + // from within pubgrub's synchronous DependencyProvider trait methods. + let handle = tokio::runtime::Handle::current(); - // 3. Run pubgrub - let root = PackageName::root(); - let root_version = ComposerVersion::stable(0, 0, 0, 0); + // Clone data needed by spawn_blocking (which requires 'static) + let repo_cache = request.repo_cache.clone(); + let platform_packages = request.platform.to_versions(); + let minimum_stability = request.minimum_stability; + let stability_flags = request.stability_flags.clone(); + let prefer_stable = request.prefer_stable; + let prefer_lowest = request.prefer_lowest; + let ignore_platform_reqs = request.ignore_platform_reqs; + let ignore_platform_req_list = request.ignore_platform_req_list.clone(); - match pubgrub::resolve(&provider, root, root_version) { - Ok(solution) => { - // 4. Convert solution to ResolvedPackage list - let mut result = Vec::new(); - for (pkg, version) in solution { - // Skip root and platform packages - if pkg.is_root() || pkg.is_platform() { - continue; - } + // 2. Run pubgrub on a blocking thread (it is CPU-bound + uses block_on for I/O) + tokio::task::spawn_blocking(move || { + let provider = MozartProvider { + handle, + package_cache: RefCell::new(HashMap::new()), + repo_cache, + platform_packages, + minimum_stability, + stability_flags, + prefer_stable, + prefer_lowest, + root_dependencies: root_deps, + root_conflicts, + ignore_platform_reqs, + ignore_platform_req_list, + }; - // Look up the original version string from the cache - let cache = provider.package_cache.borrow(); - let (version_str, version_normalized) = if let Some(pvs) = cache.get(&pkg.0) { - if let Some(vd) = pvs.versions.get(&version) { - (vd.version_string.clone(), vd.version_normalized.clone()) + let root = PackageName::root(); + let root_version = ComposerVersion::stable(0, 0, 0, 0); + + match pubgrub::resolve(&provider, root, root_version) { + Ok(solution) => { + let mut result = Vec::new(); + for (pkg, version) in solution { + if pkg.is_root() || pkg.is_platform() { + continue; + } + + let cache = provider.package_cache.borrow(); + let (version_str, version_normalized) = if let Some(pvs) = cache.get(&pkg.0) { + if let Some(vd) = pvs.versions.get(&version) { + (vd.version_string.clone(), vd.version_normalized.clone()) + } else { + (version.to_string(), version.to_string()) + } } else { (version.to_string(), version.to_string()) - } - } else { - (version.to_string(), version.to_string()) - }; + }; - result.push(ResolvedPackage { - name: pkg.0.clone(), - version: version_str, - version_normalized, - is_dev: version.stability < STABILITY_ALPHA_BASE, - }); + result.push(ResolvedPackage { + name: pkg.0.clone(), + version: version_str, + version_normalized, + is_dev: version.stability < STABILITY_ALPHA_BASE, + }); + } + Ok(result) + } + Err(PubGrubError::NoSolution(mut derivation_tree)) => { + derivation_tree.collapse_no_versions(); + let report = DefaultStringReporter::report(&derivation_tree); + Err(ResolveError::NoSolution(report)) + } + Err(PubGrubError::ErrorRetrievingDependencies { + package, + version, + source, + }) => Err(ResolveError::DependencyFetchError(format!( + "Error retrieving dependencies for {} {}: {}", + package, version, source + ))), + Err(PubGrubError::ErrorChoosingVersion { package, source }) => { + Err(ResolveError::DependencyFetchError(format!( + "Error choosing version for {}: {}", + package, source + ))) + } + Err(PubGrubError::ErrorInShouldCancel(e)) => { + Err(ResolveError::Internal(format!("Resolver cancelled: {}", e))) } - Ok(result) - } - Err(PubGrubError::NoSolution(mut derivation_tree)) => { - derivation_tree.collapse_no_versions(); - let report = DefaultStringReporter::report(&derivation_tree); - Err(ResolveError::NoSolution(report)) - } - Err(PubGrubError::ErrorRetrievingDependencies { - package, - version, - source, - }) => Err(ResolveError::DependencyFetchError(format!( - "Error retrieving dependencies for {} {}: {}", - package, version, source - ))), - Err(PubGrubError::ErrorChoosingVersion { package, source }) => { - Err(ResolveError::DependencyFetchError(format!( - "Error choosing version for {}: {}", - package, source - ))) - } - Err(PubGrubError::ErrorInShouldCancel(e)) => { - Err(ResolveError::Internal(format!("Resolver cancelled: {}", e))) } - } + }) + .await + .unwrap() } // ───────────────────────────────────────────────────────────────────────────── @@ -1096,6 +1117,13 @@ mod tests { use super::*; use pubgrub::{OfflineDependencyProvider, Ranges}; + fn test_handle() -> tokio::runtime::Handle { + static RT: std::sync::OnceLock<tokio::runtime::Runtime> = std::sync::OnceLock::new(); + RT.get_or_init(|| tokio::runtime::Runtime::new().unwrap()) + .handle() + .clone() + } + // ──────────── ComposerVersion parsing ──────────── #[test] @@ -1532,6 +1560,7 @@ mod tests { #[test] fn test_stability_filter() { let provider = MozartProvider { + handle: test_handle(), package_cache: RefCell::new(HashMap::new()), platform_packages: HashMap::new(), minimum_stability: Stability::Stable, @@ -1585,6 +1614,7 @@ mod tests { #[test] fn test_stability_filter_beta() { let provider = MozartProvider { + handle: test_handle(), package_cache: RefCell::new(HashMap::new()), platform_packages: HashMap::new(), minimum_stability: Stability::Beta, @@ -1630,6 +1660,7 @@ mod tests { #[test] fn test_stability_filter_dev() { let provider = MozartProvider { + handle: test_handle(), package_cache: RefCell::new(HashMap::new()), platform_packages: HashMap::new(), minimum_stability: Stability::Dev, @@ -1656,6 +1687,7 @@ mod tests { #[test] fn test_skip_platform_dep() { let provider = MozartProvider { + handle: test_handle(), package_cache: RefCell::new(HashMap::new()), platform_packages: HashMap::new(), minimum_stability: Stability::Stable, @@ -1677,6 +1709,7 @@ mod tests { #[test] fn test_skip_specific_platform_dep() { let provider = MozartProvider { + handle: test_handle(), package_cache: RefCell::new(HashMap::new()), platform_packages: HashMap::new(), minimum_stability: Stability::Stable, @@ -1699,6 +1732,7 @@ mod tests { #[test] fn test_root_package_choose_version() { let provider = MozartProvider { + handle: test_handle(), package_cache: RefCell::new(HashMap::new()), platform_packages: HashMap::new(), minimum_stability: Stability::Stable, @@ -1726,6 +1760,7 @@ mod tests { platform.insert("php".to_string(), php_v); let provider = MozartProvider { + handle: test_handle(), package_cache: RefCell::new(HashMap::new()), platform_packages: platform, minimum_stability: Stability::Stable, @@ -1884,9 +1919,9 @@ mod tests { // ──────────── End-to-end tests (require network, marked #[ignore]) ──────────── - #[test] + #[tokio::test] #[ignore] - fn test_resolve_monolog_e2e() { + async fn test_resolve_monolog_e2e() { let request = ResolveRequest { require: vec![("monolog/monolog".to_string(), "^3.0".to_string())], require_dev: vec![], @@ -1901,7 +1936,7 @@ mod tests { repo_cache: None, }; - let result = resolve(&request); + let result = resolve(&request).await; match result { Ok(packages) => { println!("Resolved {} packages:", packages.len()); |
