Make cache errors non-fatal in Planner::build (#12281)

Same basic approach as #11105, including a cache version bump.

Fixes #12274
This commit is contained in:
Aria Desires 2025-03-18 11:27:21 -04:00 committed by GitHub
parent faf16c1349
commit ada1acb32f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 192 additions and 118 deletions

View file

@ -1000,7 +1000,7 @@ impl CacheBucket {
match self { match self {
// Note that when bumping this, you'll also need to bump it // Note that when bumping this, you'll also need to bump it
// in `crates/uv/tests/it/cache_prune.rs`. // in `crates/uv/tests/it/cache_prune.rs`.
Self::SourceDistributions => "sdists-v8", Self::SourceDistributions => "sdists-v9",
Self::FlatIndex => "flat-index-v2", Self::FlatIndex => "flat-index-v2",
Self::Git => "git-v0", Self::Git => "git-v0",
Self::Interpreter => "interpreter-v4", Self::Interpreter => "interpreter-v4",

View file

@ -97,7 +97,7 @@ impl<'a> Planner<'a> {
[] => {} [] => {}
[installed] => { [installed] => {
let source = RequirementSource::from(dist); let source = RequirementSource::from(dist);
match RequirementSatisfaction::check(installed, &source)? { match RequirementSatisfaction::check(installed, &source) {
RequirementSatisfaction::Mismatch => { RequirementSatisfaction::Mismatch => {
debug!("Requirement installed, but mismatched:\n Installed: {installed:?}\n Requested: {source:?}"); debug!("Requirement installed, but mismatched:\n Installed: {installed:?}\n Requested: {source:?}");
} }
@ -108,6 +108,9 @@ impl<'a> Planner<'a> {
RequirementSatisfaction::OutOfDate => { RequirementSatisfaction::OutOfDate => {
debug!("Requirement installed, but not fresh: {installed}"); debug!("Requirement installed, but not fresh: {installed}");
} }
RequirementSatisfaction::CacheInvalid => {
// Already logged
}
} }
reinstalls.push(installed.clone()); reinstalls.push(installed.clone());
} }
@ -180,7 +183,8 @@ impl<'a> Planner<'a> {
.entry(format!("{}.http", wheel.filename.cache_key())); .entry(format!("{}.http", wheel.filename.cache_key()));
// Read the HTTP pointer. // Read the HTTP pointer.
if let Some(pointer) = HttpArchivePointer::read_from(&cache_entry)? { match HttpArchivePointer::read_from(&cache_entry) {
Ok(Some(pointer)) => {
let cache_info = pointer.to_cache_info(); let cache_info = pointer.to_cache_info();
let archive = pointer.into_archive(); let archive = pointer.into_archive();
if archive.satisfies(hasher.get(dist.as_ref())) { if archive.satisfies(hasher.get(dist.as_ref())) {
@ -199,6 +203,12 @@ impl<'a> Planner<'a> {
cached.push(CachedDist::Url(cached_dist)); cached.push(CachedDist::Url(cached_dist));
continue; continue;
} }
debug!("Cached URL wheel requirement does not match expected hash policy for: {wheel}");
}
Ok(None) => {}
Err(err) => {
debug!("Failed to deserialize cached URL wheel requirement for: {wheel} ({err})");
}
} }
} }
Dist::Built(BuiltDist::Path(wheel)) => { Dist::Built(BuiltDist::Path(wheel)) => {
@ -230,8 +240,10 @@ impl<'a> Planner<'a> {
) )
.entry(format!("{}.rev", wheel.filename.cache_key())); .entry(format!("{}.rev", wheel.filename.cache_key()));
if let Some(pointer) = LocalArchivePointer::read_from(&cache_entry)? { match LocalArchivePointer::read_from(&cache_entry) {
let timestamp = Timestamp::from_path(&wheel.install_path)?; Ok(Some(pointer)) => {
match Timestamp::from_path(&wheel.install_path) {
Ok(timestamp) => {
if pointer.is_up_to_date(timestamp) { if pointer.is_up_to_date(timestamp) {
let cache_info = pointer.to_cache_info(); let cache_info = pointer.to_cache_info();
let archive = pointer.into_archive(); let archive = pointer.into_archive();
@ -251,6 +263,17 @@ impl<'a> Planner<'a> {
cached.push(CachedDist::Url(cached_dist)); cached.push(CachedDist::Url(cached_dist));
continue; continue;
} }
debug!("Cached path wheel requirement does not match expected hash policy for: {wheel}");
}
}
Err(err) => {
debug!("Failed to get timestamp for wheel {wheel} ({err})");
}
}
}
Ok(None) => {}
Err(err) => {
debug!("Failed to deserialize cached path wheel requirement for: {wheel} ({err})");
} }
} }
} }
@ -281,7 +304,8 @@ impl<'a> Planner<'a> {
Dist::Source(SourceDist::DirectUrl(sdist)) => { Dist::Source(SourceDist::DirectUrl(sdist)) => {
// Find the most-compatible wheel from the cache, since we don't know // Find the most-compatible wheel from the cache, since we don't know
// the filename in advance. // the filename in advance.
if let Some(wheel) = built_index.url(sdist)? { match built_index.url(sdist) {
Ok(Some(wheel)) => {
if wheel.filename.name == sdist.name { if wheel.filename.name == sdist.name {
let cached_dist = wheel.into_url_dist(sdist); let cached_dist = wheel.into_url_dist(sdist);
debug!("URL source requirement already cached: {cached_dist}"); debug!("URL source requirement already cached: {cached_dist}");
@ -295,6 +319,13 @@ impl<'a> Planner<'a> {
wheel.filename wheel.filename
); );
} }
Ok(None) => {}
Err(err) => {
debug!(
"Failed to deserialize cached wheel filename for: {sdist} ({err})"
);
}
}
} }
Dist::Source(SourceDist::Git(sdist)) => { Dist::Source(SourceDist::Git(sdist)) => {
// Find the most-compatible wheel from the cache, since we don't know // Find the most-compatible wheel from the cache, since we don't know
@ -322,7 +353,8 @@ impl<'a> Planner<'a> {
// Find the most-compatible wheel from the cache, since we don't know // Find the most-compatible wheel from the cache, since we don't know
// the filename in advance. // the filename in advance.
if let Some(wheel) = built_index.path(sdist)? { match built_index.path(sdist) {
Ok(Some(wheel)) => {
if wheel.filename.name == sdist.name { if wheel.filename.name == sdist.name {
let cached_dist = wheel.into_path_dist(sdist); let cached_dist = wheel.into_path_dist(sdist);
debug!("Path source requirement already cached: {cached_dist}"); debug!("Path source requirement already cached: {cached_dist}");
@ -336,6 +368,13 @@ impl<'a> Planner<'a> {
wheel.filename wheel.filename
); );
} }
Ok(None) => {}
Err(err) => {
debug!(
"Failed to deserialize cached wheel filename for: {sdist} ({err})"
);
}
}
} }
Dist::Source(SourceDist::Directory(sdist)) => { Dist::Source(SourceDist::Directory(sdist)) => {
// Validate that the path exists. // Validate that the path exists.
@ -345,10 +384,13 @@ impl<'a> Planner<'a> {
// Find the most-compatible wheel from the cache, since we don't know // Find the most-compatible wheel from the cache, since we don't know
// the filename in advance. // the filename in advance.
if let Some(wheel) = built_index.directory(sdist)? { match built_index.directory(sdist) {
Ok(Some(wheel)) => {
if wheel.filename.name == sdist.name { if wheel.filename.name == sdist.name {
let cached_dist = wheel.into_directory_dist(sdist); let cached_dist = wheel.into_directory_dist(sdist);
debug!("Directory source requirement already cached: {cached_dist}"); debug!(
"Directory source requirement already cached: {cached_dist}"
);
cached.push(CachedDist::Url(cached_dist)); cached.push(CachedDist::Url(cached_dist));
continue; continue;
} }
@ -359,6 +401,13 @@ impl<'a> Planner<'a> {
wheel.filename wheel.filename
); );
} }
Ok(None) => {}
Err(err) => {
debug!(
"Failed to deserialize cached wheel filename for: {sdist} ({err})"
);
}
}
} }
} }

View file

@ -15,16 +15,14 @@ pub(crate) enum RequirementSatisfaction {
Mismatch, Mismatch,
Satisfied, Satisfied,
OutOfDate, OutOfDate,
CacheInvalid,
} }
impl RequirementSatisfaction { impl RequirementSatisfaction {
/// Returns true if a requirement is satisfied by an installed distribution. /// Returns true if a requirement is satisfied by an installed distribution.
/// ///
/// Returns an error if IO fails during a freshness check for a local path. /// Returns an error if IO fails during a freshness check for a local path.
pub(crate) fn check( pub(crate) fn check(distribution: &InstalledDist, source: &RequirementSource) -> Self {
distribution: &InstalledDist,
source: &RequirementSource,
) -> anyhow::Result<Self> {
trace!( trace!(
"Comparing installed with source: {:?} {:?}", "Comparing installed with source: {:?} {:?}",
distribution, distribution,
@ -35,9 +33,9 @@ impl RequirementSatisfaction {
// If the requirement comes from a registry, check by name. // If the requirement comes from a registry, check by name.
RequirementSource::Registry { specifier, .. } => { RequirementSource::Registry { specifier, .. } => {
if specifier.contains(distribution.version()) { if specifier.contains(distribution.version()) {
return Ok(Self::Satisfied); return Self::Satisfied;
} }
Ok(Self::Mismatch) Self::Mismatch
} }
RequirementSource::Url { RequirementSource::Url {
// We use the location since `direct_url.json` also stores this URL, e.g. // We use the location since `direct_url.json` also stores this URL, e.g.
@ -55,7 +53,7 @@ impl RequirementSatisfaction {
.. ..
}) = &distribution }) = &distribution
else { else {
return Ok(Self::Mismatch); return Self::Mismatch;
}; };
let DirectUrl::ArchiveUrl { let DirectUrl::ArchiveUrl {
url: installed_url, url: installed_url,
@ -63,37 +61,47 @@ impl RequirementSatisfaction {
subdirectory: installed_subdirectory, subdirectory: installed_subdirectory,
} = direct_url.as_ref() } = direct_url.as_ref()
else { else {
return Ok(Self::Mismatch); return Self::Mismatch;
}; };
if *editable { if *editable {
return Ok(Self::Mismatch); return Self::Mismatch;
} }
if requested_subdirectory != installed_subdirectory { if requested_subdirectory != installed_subdirectory {
return Ok(Self::Mismatch); return Self::Mismatch;
} }
if !CanonicalUrl::parse(installed_url) if !CanonicalUrl::parse(installed_url)
.is_ok_and(|installed_url| installed_url == CanonicalUrl::new(requested_url)) .is_ok_and(|installed_url| installed_url == CanonicalUrl::new(requested_url))
{ {
return Ok(Self::Mismatch); return Self::Mismatch;
} }
// If the requirement came from a local path, check freshness. // If the requirement came from a local path, check freshness.
if requested_url.scheme() == "file" { if requested_url.scheme() == "file" {
if let Ok(archive) = requested_url.to_file_path() { if let Ok(archive) = requested_url.to_file_path() {
let Some(cache_info) = cache_info.as_ref() else { let Some(cache_info) = cache_info.as_ref() else {
return Ok(Self::OutOfDate); return Self::OutOfDate;
}; };
if *cache_info != CacheInfo::from_path(&archive)? { match CacheInfo::from_path(&archive) {
return Ok(Self::OutOfDate); Ok(read_cache_info) => {
if *cache_info != read_cache_info {
return Self::OutOfDate;
}
}
Err(err) => {
debug!(
"Failed to read cached requirement for: {distribution} ({err})"
);
return Self::CacheInvalid;
}
} }
} }
} }
// Otherwise, assume the requirement is up-to-date. // Otherwise, assume the requirement is up-to-date.
Ok(Self::Satisfied) Self::Satisfied
} }
RequirementSource::Git { RequirementSource::Git {
url: _, url: _,
@ -102,7 +110,7 @@ impl RequirementSatisfaction {
} => { } => {
let InstalledDist::Url(InstalledDirectUrlDist { direct_url, .. }) = &distribution let InstalledDist::Url(InstalledDirectUrlDist { direct_url, .. }) = &distribution
else { else {
return Ok(Self::Mismatch); return Self::Mismatch;
}; };
let DirectUrl::VcsUrl { let DirectUrl::VcsUrl {
url: installed_url, url: installed_url,
@ -115,7 +123,7 @@ impl RequirementSatisfaction {
subdirectory: installed_subdirectory, subdirectory: installed_subdirectory,
} = direct_url.as_ref() } = direct_url.as_ref()
else { else {
return Ok(Self::Mismatch); return Self::Mismatch;
}; };
if requested_subdirectory != installed_subdirectory { if requested_subdirectory != installed_subdirectory {
@ -123,7 +131,7 @@ impl RequirementSatisfaction {
"Subdirectory mismatch: {:?} vs. {:?}", "Subdirectory mismatch: {:?} vs. {:?}",
installed_subdirectory, requested_subdirectory installed_subdirectory, requested_subdirectory
); );
return Ok(Self::Mismatch); return Self::Mismatch;
} }
if !RepositoryUrl::parse(installed_url).is_ok_and(|installed_url| { if !RepositoryUrl::parse(installed_url).is_ok_and(|installed_url| {
@ -134,7 +142,7 @@ impl RequirementSatisfaction {
installed_url, installed_url,
requested_git.repository() requested_git.repository()
); );
return Ok(Self::Mismatch); return Self::Mismatch;
} }
// TODO(charlie): It would be more consistent for us to compare the requested // TODO(charlie): It would be more consistent for us to compare the requested
@ -147,10 +155,10 @@ impl RequirementSatisfaction {
installed_precise, installed_precise,
requested_git.precise() requested_git.precise()
); );
return Ok(Self::OutOfDate); return Self::OutOfDate;
} }
Ok(Self::Satisfied) Self::Satisfied
} }
RequirementSource::Path { RequirementSource::Path {
install_path: requested_path, install_path: requested_path,
@ -163,7 +171,7 @@ impl RequirementSatisfaction {
.. ..
}) = &distribution }) = &distribution
else { else {
return Ok(Self::Mismatch); return Self::Mismatch;
}; };
let DirectUrl::ArchiveUrl { let DirectUrl::ArchiveUrl {
url: installed_url, url: installed_url,
@ -171,14 +179,14 @@ impl RequirementSatisfaction {
subdirectory: None, subdirectory: None,
} = direct_url.as_ref() } = direct_url.as_ref()
else { else {
return Ok(Self::Mismatch); return Self::Mismatch;
}; };
let Some(installed_path) = Url::parse(installed_url) let Some(installed_path) = Url::parse(installed_url)
.ok() .ok()
.and_then(|url| url.to_file_path().ok()) .and_then(|url| url.to_file_path().ok())
else { else {
return Ok(Self::Mismatch); return Self::Mismatch;
}; };
if !(*requested_path == installed_path if !(*requested_path == installed_path
@ -189,17 +197,25 @@ impl RequirementSatisfaction {
requested_path, requested_path,
installed_path, installed_path,
); );
return Ok(Self::Mismatch); return Self::Mismatch;
} }
let Some(cache_info) = cache_info.as_ref() else { let Some(cache_info) = cache_info.as_ref() else {
return Ok(Self::OutOfDate); return Self::OutOfDate;
}; };
if *cache_info != CacheInfo::from_path(requested_path)? { match CacheInfo::from_path(requested_path) {
return Ok(Self::OutOfDate); Ok(read_cache_info) => {
if *cache_info != read_cache_info {
return Self::OutOfDate;
}
}
Err(err) => {
debug!("Failed to read cached requirement for: {distribution} ({err})");
return Self::CacheInvalid;
}
} }
Ok(Self::Satisfied) Self::Satisfied
} }
RequirementSource::Directory { RequirementSource::Directory {
install_path: requested_path, install_path: requested_path,
@ -213,7 +229,7 @@ impl RequirementSatisfaction {
.. ..
}) = &distribution }) = &distribution
else { else {
return Ok(Self::Mismatch); return Self::Mismatch;
}; };
let DirectUrl::LocalDirectory { let DirectUrl::LocalDirectory {
url: installed_url, url: installed_url,
@ -223,7 +239,7 @@ impl RequirementSatisfaction {
}, },
} = direct_url.as_ref() } = direct_url.as_ref()
else { else {
return Ok(Self::Mismatch); return Self::Mismatch;
}; };
if *requested_editable != installed_editable.unwrap_or_default() { if *requested_editable != installed_editable.unwrap_or_default() {
@ -232,14 +248,14 @@ impl RequirementSatisfaction {
*requested_editable, *requested_editable,
installed_editable.unwrap_or_default() installed_editable.unwrap_or_default()
); );
return Ok(Self::Mismatch); return Self::Mismatch;
} }
let Some(installed_path) = Url::parse(installed_url) let Some(installed_path) = Url::parse(installed_url)
.ok() .ok()
.and_then(|url| url.to_file_path().ok()) .and_then(|url| url.to_file_path().ok())
else { else {
return Ok(Self::Mismatch); return Self::Mismatch;
}; };
if !(*requested_path == installed_path if !(*requested_path == installed_path
@ -250,17 +266,25 @@ impl RequirementSatisfaction {
requested_path, requested_path,
installed_path, installed_path,
); );
return Ok(Self::Mismatch); return Self::Mismatch;
} }
let Some(cache_info) = cache_info.as_ref() else { let Some(cache_info) = cache_info.as_ref() else {
return Ok(Self::OutOfDate); return Self::OutOfDate;
}; };
if *cache_info != CacheInfo::from_path(requested_path)? { match CacheInfo::from_path(requested_path) {
return Ok(Self::OutOfDate); Ok(read_cache_info) => {
if *cache_info != read_cache_info {
return Self::OutOfDate;
}
}
Err(err) => {
debug!("Failed to read cached requirement for: {distribution} ({err})");
return Self::CacheInvalid;
}
} }
Ok(Self::Satisfied) Self::Satisfied
} }
} }
} }

View file

@ -437,9 +437,10 @@ impl SitePackages {
[distribution] => { [distribution] => {
// Validate that the requirement is satisfied. // Validate that the requirement is satisfied.
if requirement.evaluate_markers(Some(markers), &[]) { if requirement.evaluate_markers(Some(markers), &[]) {
match RequirementSatisfaction::check(distribution, &requirement.source)? { match RequirementSatisfaction::check(distribution, &requirement.source) {
RequirementSatisfaction::Mismatch RequirementSatisfaction::Mismatch
| RequirementSatisfaction::OutOfDate => { | RequirementSatisfaction::OutOfDate
| RequirementSatisfaction::CacheInvalid => {
return Ok(SatisfiesResult::Unsatisfied(requirement.to_string())) return Ok(SatisfiesResult::Unsatisfied(requirement.to_string()))
} }
RequirementSatisfaction::Satisfied => {} RequirementSatisfaction::Satisfied => {}
@ -449,10 +450,10 @@ impl SitePackages {
// Validate that the installed version satisfies the constraints. // Validate that the installed version satisfies the constraints.
for constraint in constraints.get(name).into_iter().flatten() { for constraint in constraints.get(name).into_iter().flatten() {
if constraint.evaluate_markers(Some(markers), &[]) { if constraint.evaluate_markers(Some(markers), &[]) {
match RequirementSatisfaction::check(distribution, &constraint.source)? match RequirementSatisfaction::check(distribution, &constraint.source) {
{
RequirementSatisfaction::Mismatch RequirementSatisfaction::Mismatch
| RequirementSatisfaction::OutOfDate => { | RequirementSatisfaction::OutOfDate
| RequirementSatisfaction::CacheInvalid => {
return Ok(SatisfiesResult::Unsatisfied( return Ok(SatisfiesResult::Unsatisfied(
requirement.to_string(), requirement.to_string(),
)) ))

View file

@ -348,7 +348,7 @@ fn prune_stale_revision() -> Result<()> {
----- stderr ----- ----- stderr -----
DEBUG uv [VERSION] ([COMMIT] DATE) DEBUG uv [VERSION] ([COMMIT] DATE)
Pruning cache at: [CACHE_DIR]/ Pruning cache at: [CACHE_DIR]/
DEBUG Removing dangling source revision: [CACHE_DIR]/sdists-v8/[ENTRY] DEBUG Removing dangling source revision: [CACHE_DIR]/sdists-v9/[ENTRY]
DEBUG Removing dangling cache archive: [CACHE_DIR]/archive-v0/[ENTRY] DEBUG Removing dangling cache archive: [CACHE_DIR]/archive-v0/[ENTRY]
Removed [N] files ([SIZE]) Removed [N] files ([SIZE])
"###); "###);