Skip to content

Commit 57907f5

Browse files
daandemeyerclaude
andcommitted
Avoid invalidating all caches on mid-build-failure recovery
Previously, repository_metadata_needs_sync() returned True whenever any Cacheonly=auto image lacked a cache, which caused run_clean() to wipe every image cache to avoid partial-upgrade scenarios. As a result, if mkosi was building several images for the first time and one of them failed mid-way, the next run would re-sync metadata, wipe the caches of the images that had already succeeded, and rebuild everything from scratch. Use the manifest file as a "previously cached" marker — it's written when an image builds successfully and survives even after the cache contents go stale — and only re-sync metadata (and invalidate all caches) when every incremental image was previously cached and at least one of them is now out of date. In the mid-build-failure case, the image that failed never wrote a manifest, so we don't trigger the re-sync and the surviving caches are preserved. For all-non-incremental setups we keep the previous behavior of always syncing — there are no caches to preserve and every build downloads packages. In a mixed setup, the non-incremental images are ignored by the heuristic and may eventually fail because of stale metadata, but that's the cost of mixing them with incremental images we want to preserve. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent d4e0b8b commit 57907f5

2 files changed

Lines changed: 36 additions & 9 deletions

File tree

mkosi/__init__.py

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4798,14 +4798,38 @@ def ensure_directories_exist(config: Config) -> None:
47984798

47994799

48004800
def repository_metadata_needs_sync(images: Sequence[Config]) -> bool:
4801-
for config in images:
4802-
if config.cacheonly == Cacheonly.never:
4803-
return True
4801+
if any(c.cacheonly == Cacheonly.never for c in images):
4802+
return True
48044803

4805-
if config.cacheonly == Cacheonly.auto and not have_cache(config):
4806-
return True
4804+
if not (auto := [c for c in images if c.cacheonly == Cacheonly.auto]):
4805+
return False
48074806

4808-
return False
4807+
# If there are no incremental images at all, we have no caches to preserve, so
4808+
# always sync to avoid failures from stale metadata. In a mixed setup, ignore
4809+
# the non-incremental images and apply the heuristic to the incremental ones only.
4810+
# Non-incremental images in a mixed setup may eventually fail because of stale
4811+
# metadata, but that's the cost of mixing them with incremental images we want
4812+
# to preserve.
4813+
if not (incremental := [c for c in auto if c.is_incremental()]):
4814+
return True
4815+
4816+
# Use the manifest file as the "previously cached" marker: it's written when an
4817+
# image builds successfully and survives even after the image cache content goes
4818+
# stale. If no incremental image has a manifest, no image has ever been built
4819+
# before, so there's nothing to be consistent with and we have to sync to get
4820+
# any metadata at all. If every incremental image has a manifest and at least
4821+
# one of those caches is now out of date, the user is incrementally rebuilding
4822+
# from a fully-coherent state, so re-sync metadata and let run_clean() wipe
4823+
# everything to avoid partial upgrades. Otherwise, some images have manifests
4824+
# and some don't (e.g. recovering from a mid-build failure), and the existing
4825+
# metadata is by construction consistent with the caches that do exist, so we
4826+
# don't need to re-sync.
4827+
if all(not cache_tree_paths(c)[2].exists() for c in incremental):
4828+
return True
4829+
4830+
return all(cache_tree_paths(c)[2].exists() for c in incremental) and not all(
4831+
have_cache(c) for c in incremental
4832+
)
48094833

48104834

48114835
def sync_repository_metadata(

mkosi/resources/man/mkosi.1.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1599,9 +1599,12 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`,
15991599
as the package cache is already fully populated. If set to `metadata`,
16001600
the package manager can still download packages, but we won't sync the
16011601
repository metadata. If set to `auto`, the repository metadata is
1602-
synced unless all images are cached (see `Incremental=`) and packages can
1603-
be downloaded during the build. If set to `never`, repository metadata
1604-
is always synced and packages can be downloaded during the build.
1602+
synced when no image has ever been built before, or (and all cached
1603+
images are invalidated in this case) when every image was previously
1604+
cached (see `Incremental=`) and at least one of those caches is now
1605+
out of date. Packages can be downloaded during the build. If set to
1606+
`never`, repository metadata is always synced and packages can be
1607+
downloaded during the build.
16051608

16061609
`SandboxTrees=`, `--sandbox-tree=`
16071610
: Takes a comma-separated list of colon-separated path pairs. The first

0 commit comments

Comments
 (0)