Repository navigation
Lock menu.ps1's Hub Cache Through hub.lock So It Coordinates with menu.sh - #2537
Conversation
…u.sh menu.ps1 serialized its hub cache with a named .NET Mutex while menu.sh takes a flock on hub.lock, so a menu of each kind sharing one directory never saw the other's lock. menu.ps1 now opens hub.lock with FileShare.None, which .NET implements as an exclusive flock on Linux and which Windows refuses to any other open handle, retrying while another session holds it. The lock is re-entrant within a session, since Invoke-FetchHub is reached through Confirm-HubRoot from inside a span that already holds it, which the Mutex allowed and a second open of the file would not. Closes #1094. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A dry run never fetches or reads the hub cache, so taking the lock there created the cache directory and its lock file on a host where neither existed, which -DryRun promises not to do. menu.sh already skips its lock under --dry-run for the same reason. Lock-Hub now waits only where the open failed because another session holds the file, read from the HResult, a sharing or lock violation on Windows and EWOULDBLOCK on Linux. A full disk or a read-only mount is a plain IOException too, and was waited on forever. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lock-Hub creates the cache directory before opening its lock file, and a refusal there threw past the open's own catch, ending the whole menu session where it should fail only the task, as an unopenable lock file already does. Before the file lock, reader tasks never touched the directory at all. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configuration
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #2537 +/- ##
========================================
Coverage 58.40% 58.40%
========================================
Files 16 16
Lines 8090 8090
========================================
Hits 4725 4725
Misses 3365 3365
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
🟡 Changes recommended
Lock acquisition can wait indefinitely without a timeout or cancellation path.
1 open finding
What changed in this PR
Updates menu.ps1 to coordinate shared hub-cache locking with menu.sh through hub.lock.
Changes:
- Replaces the named mutex with an exclusive file lock.
- Adds retry, failure, dry-run, and re-entrancy handling.
- Adds cross-tool locking and failure-path tests.
| File | Summary |
|---|---|
tests/test_bootstrap.py |
Adds coverage for PowerShell locking behavior and interoperability. |
host-setup/menu.ps1 |
Implements shared cache locking and error handling. |
🧠 Review effort: Lite
Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.
…2538) ## Summary Promotes one change from `develop`. - #2537: `host-setup/menu.ps1` now locks the shared hub cache through `$DIR/hub.lock` with `FileShare.None`, in place of a named .NET `Mutex`, so it coordinates with `menu.sh`'s `flock` on the same file. .NET implements `FileShare.None` as an exclusive `flock` on Linux, and on Windows it refuses any other open handle. The lock is re-entrant within a session and skipped under `-DryRun`, matching `menu.sh`. It waits only where the open's HResult says the file is held. Any other failure fails the task, using the value the task itself returns on failure. Closes #1094. ## Verification - `TestPowerShellMenuHubLock` passed all 7 cases on Linux, in pwsh 7.4.2. That includes the two cross-tool `flock` cases. On Windows 5 cases ran and passed, and the 2 Linux-only cases were skipped. Three mutations each failed the suite. - #2537 took three recorded local strict-review passes. Copilot ran one round on its head. That round raised one finding, an unbounded lock wait, which was declined with evidence and resolved. The base's `WaitOne()` and `menu.sh`'s `flock` block with no timeout too, and a hold as long as an OS upgrade is the documented case. The maintainer approved the merge over #2480's unparsed overview after the overview was read by hand. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
… Its Token Only Once Written (#2548) ## Summary Answers the Copilot finding on promotion PR #2545: "`menu.sh` can ignore marker-write failures and leave fetched trees behind". It also fixes that finding's siblings in the same fetch functions of both menus. Refs #1093. - **Marker write.** `menu.sh`'s `fetch_hub_locked` runs with errexit suspended (`fetch_hub_locked || rc=$?`), so a failed write to `hub.owned` let the clone go ahead unmarked. Cleanup then skipped that tree, and every later fetch refused it as a tree this run did not create. The write now fails the fetch before the clone. In `menu.ps1`, a `Set-Content` failure threw past the task. It now fails the task, the same way #2537 made a lock that cannot be taken fail it. - **Old-tree removal.** A file held under the old tree made `menu.ps1`'s `Remove-Item` throw past the task and end the menu session. In `menu.sh` it made the clone run into the surviving tree, and the clone-failure path then dropped the marker. Both now fail the fetch and leave the tree and its marker as they were, so the owning session's exit cleanup can still remove them. - **When the token becomes the session's.** Each fetch builds its token in a local variable and assigns it to the session only once the marker holds it. Before this, a fetch that failed after generating its token replaced the session's own token, and the session's exit cleanup no longer matched the tree its earlier fetch had marked. ## Verification (Windows host) - New failure cases, one per path per menu: token read in bash, old-tree removal, and marker write. Each stubs or provokes the failure, then asserts the task fails, nothing is cloned, the marker is kept where it should be, and the session's token is unchanged. The `menu.ps1` stubs shadow the cmdlet in the harness body, so each case reaches its `catch`. - Mutation-checked against the code before each fix. The menus from `develop` fail both write cases. The removal guards' parent fails both removal cases. The early token assignment fails 4 cases. - Full suite under Git Bash: 2275 tests, OK, 100 skipped. ruff, mypy, the prose gate and the EOL gate pass, and so do shellcheck, shfmt and PSScriptAnalyzer through `scripts/docker_lint.py`. - Three local strict-review passes. The second raised one finding, the early token assignment, which is fixed in 31a69ad. The third raised none. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

Summary
Settles #1094.
menu.ps1serialized the shared hub cache with a named .NETMutex, whilemenu.shtakes aflockon$DIR/hub.lock, so a menu of each kind sharing one cache directory never saw the other's lock.menu.ps1now lockshub.lockitself, opened withFileShare.None, the shapebootstrap.ps1'sLock-Directoryalready uses. .NET implements that asflock(LOCK_EX|LOCK_NB)on Linux, which conflicts withmenu.sh's shared reader lock and its exclusive writer lock in both directions. On Windows it refuses any other open handle on the file. Every caller still takes one exclusive lock, as with the mutex.0x80070020(low word 32, or 33 for a lock violation) on Windows, and raw errno 11 (EWOULDBLOCK) on Linux. Any other failure, the cache directory's own creation included, fails the task and returns the caller's own failure value. A$falsethere would read as exit code 0 to a caller comparing with-eq.Invoke-FetchHubis reached throughConfirm-HubRootfrom inside a span that already holds it, which the mutex allowed and a second open of the file would not.-DryRunruns unlocked, matchingmenu.sh'shub_read_lock_acquire, since a dry run never fetches or reads the cache and must not create the directory or the lock file.Verification
TestPowerShellMenuHubLock, 7 cases. On Linux (pwsh 7.4.2 in the offlinepython:3.12-slimimage): all 7 pass, including the two cross-tool cases, a sharedflockmaking the menu wait and the menu's lock refusing a sharedflock. On Windows (pwsh 7, from Git Bash): 5 pass, and the 2 Linux-only cases skip.FileShare.Readin place ofNone(3 failures on Linux), the re-entrancy guard removed (the nested case times out), and theNew-Itemcatch removed.tests.test_bootstrappasses on Windows. ruff, mypy, PSScriptAnalyzer, the prose gate, and the EOL gate are clean.IOException, an uncaught directory-creation failure ending the session, and two test gaps. Four were fixed in this branch. One was declined: a test for a non-contentionIOException, which no portable fixture produces. The wait tests cover that classification from the other side. The third pass raised nothing.The
commentslabel stands for the new lock's comments.Closes #1094 on promotion.
🤖 Generated with Claude Code