fix(spm): serialize concurrent scans to protect synthetic Package.swift (DEVA11Y-483) - #33
fix(spm): serialize concurrent scans to protect synthetic Package.swift (DEVA11Y-483)#33Crash0v3rrid3 wants to merge 3 commits into
Conversation
…ft (DEVA11Y-483) Guard the setup/scan/cleanup cycle with an atomic per-directory mkdir lock so concurrent spm.sh invocations in the same working directory no longer race: the first instance to exit can no longer delete the shared synthetic Package.swift out from under a still-running peer. Stale locks left by killed peers are reclaimed after 5 minutes; mkdir is used instead of flock(1) for macOS portability. Applied to the bash, zsh and fish variants. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…A11Y-483) Addresses all code-review findings on the first cut: - #1 Decide manifest ownership UNDER the lock from the live filesystem, not a startup PACKAGE_EXISTS snapshot. The lock is now taken unconditionally, so a peer that starts after the synthetic Package.swift already exists still serializes instead of running unprotected and getting its file deleted. - #2 Reclaim a crashed peer's lock by PID liveness (kill -0), not a 5-min mtime that would steal a slow-but-alive long scan's lock. - #3 Claim a stale lock atomically via rename so two waiters can't both reclaim. - #4 Wait-timeout is non-fatal: it skips the scan (exit 0) with a visible 'waiting...'/'skipping' notice instead of hanging a git commit then aborting it. - #5 A non-EEXIST mkdir failure (unwritable/read-only/full TMPDIR) fails fast with an actionable message instead of waiting out the full timeout. - #6 The lock lives under TMPDIR keyed by the package path, never inside the working tree, so a crash can't leave it to be git-added. - #7 Consistent 'A11y scan:' message prefix. - Also fixes a latent bug from the first cut: cleanup state (lock_dir/have_lock/ created_package) is now global, since the EXIT trap fires after a11y_scan returns when its locals are out of scope (verified) -- previously the lock was never released on a normal run. Verified with an integration test (staggered concurrent runs serialize; both scans see Package.swift throughout; no tree/TMPDIR residue) and unit tests for stale reclaim, live-owner detection, and fail-fast. Applied to bash/zsh/fish. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Addressed all review findings in
Also fixed a latent bug the review surfaced: Verified: integration test (staggered concurrent runs serialize; both scans see |
…(DEVA11Y-483) The concurrency-lock redesign edited all three spm.sh launchers but left their .sha256 sidecars stale, which would make the self-update integrity check abort on every run (dead on arrival, same class as the DEVA11Y-475 fix). Regenerate all three sidecars to match. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude Code PR ReviewPR: #33 • Head: 7ed6134 • Reviewers: stack:pr-review (aggregated correctness/security/testing/perf/quality) SummarySerializes concurrent Review Table
Sidecar integrity verified: recomputed Findings
Verdict: PASS — lock design is correct, sidecars verified matching, no High/Critical. Recommend fixing the Medium reclaim busy-spin before/shortly after merge. |
Claude Code PR ReviewPR: #33 • Head: 7ed6134 • Reviewers: fallback general-purpose reviewer + orchestrator checksum verification (no in-repo SummaryReworks the concurrent-scan lock in the three Review Table
Checksum sidecars verified: recomputed SHA-256 of each committed Findings
Additional low/nits (non-gating): PID reuse can make a dead owner look alive (inherent to PID locking; worth a comment); the 300s non-fatal timeout lets a commit skip the scan under a genuinely slow peer (documented design decision); the 32-bit Verdict: PASS — the redesign is sound and closes the prior review's findings; the one Medium (pid-less poison lock) is a worthwhile follow-up but non-gating. |
What & why
Fixes DEVA11Y-483 (F-014, CWE-362, Medium) — concurrent
spm.shinstances in the same directory race on a shared syntheticPackage.swift.PACKAGE_EXISTSis captured once at startup; theEXITcleanup trap then removesPackage.swift/Package.resolvedunconditionally when the flag is non‑zero. Two instances in the same CWD both see no manifest, both write one, and the first to exit deletes it out from under the still‑running peer — which then fails with a confusing "no Package.swift in current directory" from SPM.Change
Guard the setup/scan/cleanup cycle with an atomic per‑directory
mkdirlock (only when we own the synthetic manifest, i.e.PACKAGE_EXISTS != 0):mkdiris atomic on POSIX filesystems and portable to macOS, which has noflock(1)(the ticket suggested a CWD mutex — this is the portable form).cleanupremoves the manifest first, then releases the lock; only the lock owner releases it.The scan still runs in the working directory (temp‑dir +
--package-pathwas rejected because it would change the plugin's scan target away from the user's sources).Testing
bash -nsyntax check passes on all three scripts.Draft for review.
🤖 Generated with Claude Code