Make the cpx cache layer atomic and concurrent-safe#22
Merged
Conversation
- Add Lock::run for flock-based exclusive critical sections - Add Filesystem::writeAtomic for temp-write-then-rename writes - Add Filesystem::deleteDirectoryWithin to refuse deletions outside a root
- Persist metadata under a lock with atomic writes via Metadata::transaction - Introduce typed PackageMetadata and ExecSandboxMetadata records - Version the schema and reserve an aliases section for forward compatibility - Hash version constraints into safe cache-key folder names - Stage installs in a temp directory and rename atomically to avoid partials
- Clean stale and orphaned package/exec caches with path-safety guards - Route the exec sandbox through the typed ExecSandboxMetadata record - Render the package list from the typed metadata accessors
Fall back to a package's last-updated time when it has never been run, so a recent install is not reclaimed before its binary is ever used.
Extract CleanCommand::SECONDS_PER_DAY so the --days window conversion reads clearly. No behavior change.
Bare `clean` now asks what to clean (all, sandbox, or a day window) via Laravel Prompts, runs cleanup inside a task spinner, and reports a callout summary. Adds a --sandbox flag and makes --days optional; non-interactive runs still reclaim caches older than 30 days.
- Stage and atomically rename exec sandbox installs so a failed install can't poison the cache - Mutate sandbox state inside the metadata transaction to keep concurrent writes safe - Store all cache timestamps as unix ints, normalizing legacy date strings on read - Share one staleness rule across packages and sandboxes, and skip malformed metadata entries - Exit clean non-zero when a cache can't be removed - Add a race-safe directory helper and drop the unsafe Metadata::save()
joetannenbaum
left a comment
Collaborator
There was a problem hiding this comment.
Just the one question about aliases, otherwise good to go 👍
| public array $packages = [], | ||
| public array $execCache = [], | ||
| ) {} | ||
| public array $aliases = [], |
Collaborator
There was a problem hiding this comment.
Is this just setup for another PR? Looks like it's just scaffolding in this context, should we hold off on this until we know how we're going to use it?
Member
Author
There was a problem hiding this comment.
Yeah, that was just scaffolding, I'll remove it!
Collaborator
There was a problem hiding this comment.
Once removed merge away 🫡
The aliases section only round-tripped with no consumers. It'll come back with the user-managed aliases work it was setting up for.
clean only deleted the leaf version directory, leaving empty vendor/name skeletons behind. It now walks up and removes each emptied parent, stopping at the cache root and skipping any a concurrent install has repopulated.
ob_get_clean() can return false, which OutputInterface::write() rejects, so restore the check before flushing captured output.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
cpx keeps a shared metadata file next to its package and exec-sandbox caches. Concurrent runs could clobber each other's writes, a partially-finished install could leave a poisoned cache that never recovered on its own, and
cleanwas a blunt all-or-nothing command.This reworks the cache layer to be safe under concurrent use: metadata updates run inside a file lock with atomic temp-file-then-rename writes, and packages and exec sandboxes install into a staging directory that's atomically renamed into place, so a failed install can't leave anything broken behind.
cleanalso gets an interactive Prompts flow and path-guarded deletion that stays inside the cpx cache root.