Repository navigation
Fix OCI prune cleanup of broken tag symlinks - #1312
mikemikimike wants to merge 10 commits into
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 33c49e5f81
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
33c49e5 to
beac7ea
Compare
beac7ea to
4626d57
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4626d57bf7
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
@mikemikimike just need to fix the unit test |
|
@mikemikimike test still failing |
df3a16a to
01c822a
Compare
|
Thanks @yzhuang-oai. I addressed both reported I could not run the Swift XCTest suite on this Windows host because Swift/Xcode is unavailable here. Please rerun the macOS CI job to confirm the updated fixtures in the project environment. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d6c9bde8ad
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
Pushed commit 5238e0a to address the requested cleanup scope. Cached-image pruning now removes only tag symlinks targeting the deleted digest, then runs content-only GC; it no longer invokes full record GC and cannot delete unrelated prune candidates. The Windows environment cannot run the repository's macOS/Xcode test suite locally. The new CI run is currently awaiting fork workflow approval (action_required, no jobs); please approve and run it so the regression tests can execute. |
|
@mikemikimike CI still failing with unit test failures |
|
Fixed the CI regression in commit |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 63ab43a114
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| fileURLWithPath: destination, | ||
| relativeTo: foundURL.deletingLastPathComponent() | ||
| ).standardizedFileURL | ||
| if destinationURL == standardizedTargetURL { |
There was a problem hiding this comment.
Compare symlink targets without directory URL hints
When pruning a digest record that has a tag, vmDir.delete() removes the target directory before this comparison runs. Constructing destinationURL after that removal produces a file URL without a directory marker, while targetURL came from the directory enumerator and retains its trailing-slash directory hint; Foundation treats those URLs as unequal, so the branch is skipped and the broken tag symlink remains. The new tests do not catch this because fileExists(atPath:) follows a dangling symlink and returns false even when the link itself still exists; compare normalized path strings or otherwise inspect the symlink itself.
Useful? React with 👍 / 👎.
|
Addressed the review feedback in commit |
|
@mikemikimike test still failing |
|
Fixed in commit The two remaining test failures were caused by comparing a directory URL with a trailing separator against the equivalent symlink destination without one. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 89d927b683
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| while let prunable = try prunableStorages | ||
| .flatMap({ try $0.prunables() }) | ||
| .first(where: { try $0.accessDate() <= olderThanDate }) { |
There was a problem hiding this comment.
Avoid rescanning every storage after each age deletion
When many entries are older than the cutoff, this loop rebuilds the complete prunable list after deleting each individual entry. For OCI storage, every prunables() call walks the cache, parses cached manifests, scans local and temporary references, and inventories shared content, so pruning N expired images now performs roughly O(N²) filesystem and manifest work instead of the previous single scan. Large CI caches can therefore make tart prune --older-than prohibitively slow; retain a snapshot while skipping candidates removed as a side effect, or otherwise limit recomputation to the storage whose ownership data changed.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ed94578ec2
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| return nil | ||
| } | ||
|
|
||
| guard normalizedPath(vmDir.baseURL.resolvingSymlinksInPath()) == standardizedTargetPath else { |
There was a problem hiding this comment.
Canonicalize both paths when matching tag links
When TART_HOME or its cache directory contains a symlink—a layout explicitly documented for tests in Sources/tart/VM.swift:162-165—standardizedTargetPath retains that alias while vmDir.baseURL.resolvingSymlinksInPath() resolves it, so this guard rejects every tag pointing to the record and pruning leaves dangling links. Unlike the earlier directory-URL-hint issue, the fresh evidence is this final patch's asymmetric normalization; resolve both sides consistently here and during the pre-removal destination recheck.
Useful? React with 👍 / 👎.
|
Addressed the remaining OCI prune review feedback in commit Changes:
Validation:
Please review the latest commit. |
|
@mikemikimike if you didn't catch it, latest commits are failing CI. i can look into it if you'd like - very eager to see this one merged! |
290fbbc to
dfc676e
Compare
|
@mikemikimike CI is still failing and the change is getting really complicated, can we think about a simple fix here? And you can test this locally to ensure tests will pass and then update the PR. |
9619492 to
dfc676e
Compare
|
The latest fix is pushed to this PR as |
|
@mikemikimike this PR cannot merge as it is, it is unnecessarily complex, can we think of an easier implementation? |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a9dd777e09
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| try prunableToDelete.delete() | ||
| } | ||
|
|
||
| try collectOCIContentIfNeeded(using: ociStorage, afterDeletingCachedImage: didPruneCachedImage) |
There was a problem hiding this comment.
Collect orphaned OCI content before recomputing budgets
When a pruned cached image's owned content has a newer access date than an otherwise retainable entry (content atimes are updated independently from record atimes), deferring gcContent() until here makes the next while iteration count that now-unreferenced content as a ContentPrunable. For example, with a 50 GB budget, a freshly accessed 40 GB orphan is retained first and a 50 GB valid image is then deleted; this call finally deletes the orphan too, although the valid image alone fit the budget. Remove or ignore content released by each cached-image deletion before recomputing prunables(); the same ordering affects automatic reclaim.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 96abc27cc4
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| guard let enumerator = FileManager.default.enumerator( | ||
| at: baseURL, | ||
| includingPropertiesForKeys: [.isSymbolicLinkKey] |
There was a problem hiding this comment.
Reuse the tag index across batch pruning
When --older-than deletes N cached images, every CachedImagePrunable.delete() calls this helper, which walks the entire remaining OCI tree while holding the global prune lock. Although pruneOlderThan now retains one candidate snapshot, these N full walks are fresh evidence that age pruning is still O(N²) in directory entries and repeatedly blocks cache users on the lock, so large CI caches can remain very slow; collect the tag-to-digest mapping once for the batch or otherwise limit each lookup to an index or relevant namespace.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a862cb82c9
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if let ociStorage, isCachedImage(prunable, in: ociStorage) { | ||
| didPruneCachedImage = true | ||
| } | ||
| try prunable.delete() |
There was a problem hiding this comment.
Run batched OCI cleanup after partial age-prune failures
When --older-than successfully deletes an OCI cached image and a later candidate throws (for example, another cached image is PID-locked by a concurrent push), this try exits before the cleanup at the end of the function, while CachedImagePrunable.delete() no longer calls gcContent() itself. The first image's owned immutable files are therefore left behind; if their own access dates are newer than the cutoff, rerunning the same age prune skips them and does not invoke the batch GC unless another image is deleted. Ensure cleanup runs after any successful image deletion even when a later deletion fails, then rethrow the original error.
Useful? React with 👍 / 👎.
Summary / Problem
tart prunecan delete a digest-addressed OCI cache record while leaving tag symlinks pointing to the deleted directory. The cache then contains broken links until a separate garbage-collection run.Changes
Tests
git diff --check— passedswift test --build-system swiftbuild— not run locally because Swift/Xcode is unavailable on the Windows development host33786425450passed the build but still failed only in the two tag-cleanup regression tests; the latest fix is in commit2fadfec33787175157for the latest fix is currently blocked by GitHub fork-workflow approval before jobs startCompatibility / Known limitations
No CLI or storage-format changes. Healthy tag symlinks and unrelated cached images are preserved. The macOS XCTest suite cannot be confirmed for the latest fix until an upstream repository administrator approves the fork workflow.
Issue link
Closes #1293