Skip to content

fix(trace): serialize V2GitStore storer access under StorerMu#52

Merged
Patel230 merged 4 commits into
mainfrom
fix/trace-storer-race
Jul 15, 2026
Merged

fix(trace): serialize V2GitStore storer access under StorerMu#52
Patel230 merged 4 commits into
mainfrom
fix/trace-storer-race

Conversation

@Patel230

Copy link
Copy Markdown
Contributor

Summary

Wraps every public V2GitStore method that touches the git storer in StorerMu, mirroring the v1 GitStore convention. Required private *Locked impl extraction for methods involved in intra-class public-to-public calls to avoid deadlocking the non-reemd mutex (caught 2 deadlocks via the test suite during development: inside rotateCurrentIfNeeded and fullRefSearchOrder -> ListArchivedGenerations).

Why

The v1 path holds StorerMu at ~30 sites because go-git storer is not concurrency-safe. V2GitStore never acquired it, risking git object corruption in the core checkpoint feature. Audit finding.

Verification

  • All checkpoint tests pass (8.4s), 58 cli packages pass
  • go build ./..., go build ./cmd/trace, golangci-lint run ./cli/checkpoint/ clean

Patel230 added 4 commits July 15, 2026 11:16
Adds a jj-style operation log (cli/oplog), the only record of trace's own
ref mutations — previously the sole safety net was git's own reflog,
which isn't queryable via trace and never covers the shadow-branch
SetReference rewrites rewind/cleanup perform directly (those don't touch
HEAD). Entries are stored on the existing trace/checkpoints/v1 orphan
branch alongside checkpoints, so they sync the same way via the
already-working push/fetch/merge path. 'trace undo' reverts the most
recent recorded operation; 'trace log' lists the operation history.
runUndo restored only the git ref via SetReference for every operation
type. That's correct for rewind/cleanup (shadow-branch-only rewrites that
never touch HEAD), but wrong for reset-hard: git reset --hard moves the
ref, index, and working tree together, so undoing it via SetReference
alone left files on disk in the post-reset state while the branch pointer
claimed otherwise. Now reuses performGitResetHard for OpResetHard entries,
the same code path the original operation used.
go-git's filesystem storer is not safe for concurrent read+write, even
across separate Repository instances sharing the same .git directory.
The v1 GitStore path already wraps every operation in StorerMu; the v2
path touched the storer directly and never acquired it.

Lock every public V2GitStore method that reads or writes the storer.
Because several public methods call other public methods under their own
lock, extract private *Locked helpers and route internal callers through
them to avoid deadlock (WriteCommitted->WriteCommittedWithSessionIndex,
rotateGeneration->NextGenerationNumber->ListArchivedGenerations,
ReadGenerationFromRef->ReadGeneration, ReadSessionContentByID/GetSessionLog->
ReadCommitted, and the private rotateCurrentIfNeeded->rotateGeneration/
CountCheckpointsInTree paths).

The pending-rotation path is intentionally left unlocked: it uses a
separate .lock file (withPendingFullGenerationPublicationLock) and never
touches the git storer.
@Patel230
Patel230 merged commit bec18b8 into main Jul 15, 2026
13 checks passed
@Patel230
Patel230 deleted the fix/trace-storer-race branch July 15, 2026 19:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant