Repository navigation
Conversation
…k in _common.sh A dead holder's line X is now reaped under tickets <lock>.reap.<cksum X>.<N>, written aside and ln'ed (exclusive, never empty). Ticket N+1 is allowed only when ticket N's pid is dead, never by age. Only the highest ticket's holder may rm X, and only while the lock still holds exactly X. A new holder clears other keys' tickets. Lock lines gain a nonce= field. exec.sh and test-scratch.sh call mxtk_lock_take/mxtk_lock_release; both stop with the sync remedy on an older _common.sh. bench-lock-race.sh, 200 rounds x 10 contenders, Linux: old block (master d16f578): 13 overlaps in 6 rounds, 12 stolen locks reap tickets: 0 overlaps, 0 stolen, 0 starved, 0 probe failures Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
…me a blocking reaper 1. An empty X is not unique: the old code (and the no-hard-link fallback) creates its lock empty and writes after, so a fresh empty lock could replace the stale one while a reaper sat between compare and rm. Before the rm, an empty X must still be over a minute old (find -mmin +1). test-lock-reap.sh swaps one in at the compare anchor; the case fails on a677671 and passes here. 2. When mxtk_lock_take gives up because a live reaper's ticket (or a live old-style .takeover) never finished, MXTK_LOCK_BLOCKER names the pid and file, and mxtk_lock_blocked_text says to resume (kill -CONT) or end it. exec.sh and test-scratch.sh print it on rc 2. Bench, 60 rounds x 10 (Linux): default anchors 0 overlaps / 0 stolen / 0 starved / 0 probe failures; all four anchors the same. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
This was referenced Oct 14, 2026
Merged
Merged
_common.sh: end-of-file conflict with the mxtest helpers (#370), both sections kept. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
Review nit on #379: it described a blocker from an earlier attempt when the last attempt ended for another reason (a fresh empty lock being written). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
MendixMau
added a commit
that referenced
this pull request
Oct 15, 2026
sync-project.sh --only-bin <name> installs or refreshes just the named project-bin scripts, without running a full sync. It fixes the #390 field finding that --upgrade-bin ran a whole sync. Review: independent review MERGE after one fix round. - --only-bin scope probed on both layouts: only bin/<name> changes. A dry run, a path-like or unknown name, a NOINSTALL name, a glob and an empty name all write nothing. - The full sync is byte-identical to master over 18 runs. - All five fix-round findings verified: the refresh hold when _common.sh is edited, manifest validation, set -f, the empty-name usage error and the dry-run backup note. - test-sync-only-bin 40/0 and test-bug12-sync 61/0. Portability, pr-discipline, leak guard and render-routing are clean. - Follow-up: #379 switches its two plain --upgrade-bin _common.sh remedies to --only-bin.
Brings in #396 (sync-project.sh --only-bin). No conflicts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
exec.sh and test-scratch.sh told users to run sync-project.sh with --upgrade-bin _common.sh alone, which runs a full sync. They now give --only-bin _common.sh --upgrade-bin _common.sh, matching test-scratch.sh's mxtest guard on master. app-restart.sh is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
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.
Direct lane
What changed and why (one paragraph):
Before this change, a stale lock was taken over under one
<lock>.takeoverfile. That file went stale by pid or by age, and it was moved aside and put back withln. If a third contender created a new takeover file in that gap, thelnfailed and two contenders were inside the takeover at once. One contender's compare-then-rm could then delete the other's fresh lock (r-eng-backlog item 1). An earlier fix that re-checked our own line just before the rm was rejected, because it only narrowed the window. This PR replaces the takeover file with reap tickets:<lock>.reap.<cksum X>.<N>. The ticket is written to<lock>.tmp.<pid>and thenlned into place, so it is exclusive and never empty.rm, and only if the lock still holds exactly X. It then creates its own lock exclusively.nonce=field, so a line can never come back. This is why the compare-then-rm cannot hit a fresh lock: while the lock holds X, only one live process can be past the ticket check for X.The code now lives once, in
project-bin/_common.sh, asmxtk_lock_take/mxtk_lock_release/mxtk_lock_live(+mxtk_lock_fields,mxtk_lock_blocked_text). It is pure bash 3.2 with POSIX tools and noflock. It replaces exec.sh'sexec_lock_*and test-scratch.sh'stake_over/holder_live. The exec.sh diff is only the call sites plus the older-_common.shguard (the app-restart.sh pattern), so it should not collide much with #355, the exec-on-copy PR or the baseline-lock PR.Review fixes (second commit, 81981f2):
find -mmin +1). The new case intest-lock-reap.shswaps in a fresh empty lock at thecompareanchor. It fails on a677671 and passes now..takeover, never finished,MXTK_LOCK_BLOCKERnames the pid and file. That covers a SIGSTOPped reaper, or a reused pid whereps -o args=cannot tell processes apart.mxtk_lock_blocked_textthen says to resume the process (kill -CONT <pid>/fg) or end it (kill <pid>). exec.sh and test-scratch.sh print this on rc 2.mxtk_pid_argsnot used. Reusingmxtk_pid_argsin the liveness check was left out on purpose. On Windows it runs PowerShell, about a second per call, inside a retry loop. On Linux it gives the same answer asps -o args=for this match. So it does not come out clean.Mixed versions:
.takeoveris live, and clears a stale one once it holds the lock.test-lock-reap.shruns 9 old-block and 9 new contenders against one dead holder, and all 18 acquire.Test anchors:
MXTK_LOCK_TEST_PAUSEnames a command that runs as<cmd> <anchor> <pid>at four points:ticket-taken,compare,rmandcreated. When it is unset it does nothing.MXTK_LOCK_RETRY_SLEEPsets the pause between attempts (default 1 s).Regression harness:
tests/wave2/bench-lock-race.sh.ticket-taken/compare/rm(25% each), and are also SIGKILLed at random 0–50 ms offsets (10%).--oldruns master's block, the verbatim copy infixtures/mxtest-guard/exec-lock-block.sh, identical to d16f578. Anchors are sed-injected at the same points, plusjudgedandaside(the old move-aside step).Measured, Linux, 200 rounds × 10 contenders, same settings (first commit):
--old)The reap-ticket run left 0 files beside the lock at the end.
After the review fixes, 60 rounds × 10 contenders:
created)Fixtures:
tests/wave2/test-lock-reap.sh(45/45) covers each rule separately: the line format and nonce; release removes only our own line; a live holder refuses; every old and new line format is reaped; a live ticket dated 2000 still blocks (no age rule) and names its pid, file and remedy; dead tickets 1 and 2 lead to ticket 3; the line changing at the compare anchor; a higher ticket appearing stops the rm; a reaper SIGKILLed atrmdoes not block the next; GC; an empty lock; an empty lock swapped in mid-reap; legacy.takeoverinterop; mixed-version no-deadlock; and a 15-round harness run.test-shared-lock-takeover.shandtest-test-scratch.shnow source_common.shinstead of awk-lifting the lock code.Field runs
--old(master's block)Field evidence: no consuming-project run; this is concurrency code, and the proof is the harness. The macOS run covers APFS hard links,
ps -o args=on a macOS zombie and fractionalsleep. Still owed before merge: one Git Bash run ofbash tests/wave2/bench-lock-race.sh(and--old) to cover NTFS hard links andkill -0alone deciding liveness. That is why this PR stays a draft.Merge with master (7f4169d):
origin/master(24fe336) is merged in with a merge commit. The end-of-file conflict inproject-bin/_common.shwith #370's mxtest helpers was resolved by keeping both sections. After that, c932fa1 clearsMXTK_LOCK_BLOCKERat the start of every take attempt (review nit), so it describes only the last attempt.CHANGELOG.mdline appended, creditedImpact
project-bin/_common.sh(newmxtk_lock_*),project-bin/exec.sh(call sites),project-bin/test-scratch.sh. The end-of-file conflict with master's mxtest-identity section inproject-bin/_common.shis resolved by merge 7f4169d (both sections kept; fixtures green on c932fa1). Also conflicts with #384 in_common.sh;exec.shmerges clean with #374 and #375. Platform: bash 3.2 + POSIX; relies onlnhard links,ps -o args=,find -mmin, fractionalsleep; macOS + Git Bash runs owed. Reads only its own lock and ticket files.git merge-treevs 868b3cb (CONFLICT in_common.sh) and pairwisebin/render-routing.sh --checkon master 868b3cb vs this head (81981f2) merged onto 868b3cb viagit merge-treein a temporary worktree (conflict markers left inproject-bin/_common.sh, which render-routing does not read)MXTK_LOCK_RETRY_SLEEPdefault 1 s.tests/wave2/bench-lock-race.shresults in the PR🤖 Generated with Claude Code
https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
Generated by Claude Code