Skip to content

fix(blob): never gate apply commits on wire-carried save flags; gate local writes on blob durability - #1641

Merged
kriszyp merged 4 commits into
mainfrom
fix/blob-commit-gating
Jul 7, 2026
Merged

kriszyp merged 4 commits into
mainfrom
fix/blob-commit-gating

Conversation

@ldt1996

@ldt1996 ldt1996 commented Jul 7, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #1640

  • The apply path (trackPersistedBlobs) now runs first in startPreCommitBlobsForRecord and only tracks blobs for cleanup, never gating the commit, and pack() no longer ships saveBeforeCommit/saveInRecord. Wire-carried flags could previously gate a receiver's commit on an in-flight receive stream, freezing the apply loop (see issue).
  • Local writes with a fresh blob now gate the record commit on the blob's durable save, so a record can no longer commit (and replicate) with a not-yet-durable blob reference.
  • Already-saved blobs (fileId set) are exempt from that gate: a local write carrying one references another row's blob, and tracking it would let abort cleanup delete a file the owning record still needs.

Behavior change: a local write whose fresh blob fails to save now rejects instead of committing a broken reference, matching the existing saveBeforeCommit fault contract. Four tests updated accordingly.

Validated on the 4-node staging repro from the issue: blob tables converge to exact row equality within minutes of load stop. blob.test.js 62 passing; the 3 test:unit:resources failures reproduce on origin/main and are unrelated.

🤖 Generated with Claude Code

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors blob pre-commit logic to prevent deadlocks on replication-apply paths and replication divergence (orphan blob references) on local-write paths. It ensures that local record commits gate on the blob's durable landing and strips origin-local write instructions (saveBeforeCommit and saveInRecord) before serialization. The feedback suggests checking if a blob's saving promise is pending in addition to checking for a null fileId to avoid skipping durability gates on manually saved blobs. Additionally, it recommends using object destructuring instead of the delete operator in hot paths to prevent V8 deoptimizations.

Comment thread resources/blob.ts Outdated
Comment thread resources/blob.ts Outdated
@claude

claude Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Reviewed; no blockers found.

@ldt1996

ldt1996 commented Jul 7, 2026 •

Copy link
Copy Markdown
Contributor Author

Both addressed in f6b1b96: the still-streaming gap was real, but keying on saving != null alone would break the cross-row reuse contract (the saving promise sticks around after it settles), so the gate uses a saved marker instead, with a regression test; also swapped the deletes for destructuring.
The failing unit check is the same LMDB flake that's been red on main for the last 5 runs: https://github.com/HarperFast/harper/actions/runs/28830783277

Lavinia, via Claude

@ldt1996 ldt1996 added the patch label Jul 7, 2026
@github-actions

github-actions Bot commented Jul 7, 2026 •

Copy link
Copy Markdown
Contributor

Patch cherry-pick: merged

Cherry-picked onto v5.1.

@kriszyp kriszyp left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed via Claude review-queue — sound blob-durability fix, local writes now correctly block commit on save durability. CI red is a pre-existing unrelated subscriptionReplay flake. LGTM.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Replicated blobs created with saveBeforeCommit deadlock the receiver's apply loop

2 participants