Conversation
_mou records that Sync Gateway changed a document's metadata without touching its body: _mou.cas names the mutation just made, and pCas/pRev name the mutation that last wrote the body. pRev did not keep up with pCas: - computeMetadataOnlyUpdate carried pCas forward when replacing a metadata-only update but always wrote the document's current revision sequence number, so chained metadata-only updates left pCas naming the body write and pRev naming the update before them. - restampVersionCAS wrote pRev as 0, because updateAndReturnDoc reads the document without the revSeqNo virtual xattr and so never populates doc.RevSeqNo in the write path. The re-stamp is now a read-modify-write that takes the revision sequence number the server assigned the write it is correcting, declining the correction when the document has moved on since - previously a CAS mismatch, now ErrUpdateCancel. Both mean the write being corrected has been superseded, so both are skipped, behind a shared isSupersededWriteError predicate. - MigrateAttachmentMetadata hand-built its _mou and never read the existing one, so it could not carry anything forward. It now uses computeMetadataOnlyUpdate like the other metadata-only writers. Only pRev was ever wrong. Running the tests below against the parent commit places the damage, where a chained update is one landing on a document whose previous mutation was already a metadata-only update: | metadata-only write path | pRev, first update | pRev, chained | |-------------------------------|--------------------|---------------| | on-demand import for get | correct | wrong | | on-demand import for write | correct | wrong | | attachment metadata migration | correct | wrong | | channel history compaction | correct | wrong | | resync | correct | wrong | | version-CAS correction | wrong, always 0 | wrong | Nothing else about these paths was wrong: | behaviour | before this commit | |----------------------------------------|--------------------| | _mou.cas naming the mutation just made | correct | | pCas, chained updates included | correct | | re-stamping a tombstone | already worked | | declining a superseded re-stamp | already worked | Re-stamping a tombstone already worked because updateXattrs sets SubdocDocFlagAccessDeleted, and WriteTombstoneWithXattrs reaches that same call for an existing tombstone. A re-stamp superseded by a concurrent write was already declined, on a CAS mismatch. TestMetadataOnlyUpdateWritePaths covers the paths whose metadata-only write lands on a document some earlier write left behind - both on-demand import paths, attachment metadata migration, channel history compaction and resync - asserting that _mou.cas names the mutation just made, that pCas and pRev name the last write to the body, and that both survive a following metadata-only write from a different path. TestRestampVersionCASMou covers the version-CAS correction separately, over a live document and a tombstone. It is the one path whose metadata-only write follows a body write it makes itself, so _mou names that write rather than anything observable before it, and both previous values are pinned by sandwiching them between the mutation preceding the write under test and the re-stamp that follows it. Channel history compaction stands in for resync as the second metadata-only write there, as resync declines a tombstone. TestAttachmentMigrationMouCarriedForward covers migration of a document whose previous mutation was already a metadata-only update, as arrives by mobile XDCR from a cluster that had not migrated its attachment metadata. TestRestampVersionCASSkipsConcurrentWrite covers the declined correction, asserting that the re-stamp is refused in a way correctVersionAheadOfCAS skips on and that the concurrent write is left untouched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Aug 26, 2026
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.
Base of a four-PR stack for CBG-5764.
_mou.pCasand_mou.pRevhave to describe the same mutation - the write that last changed the document body. When a metadata-only write replaced a mutation that was itself metadata-only,pRevwas refreshed to the current revSeqNo whilepCaswas carried forward, so the two pointed at different mutations.computeMetadataOnlyUpdatenow carries both previous values forward together when the mutation being replaced was itself a metadata-only update.correctVersionAheadOfCAS/restampVersionCASre-stamp the CAS of a write whose generated version ran ahead of it, reading$document.revidunder the CAS guard sopRevnames the write actually being corrected.Verified on rosmar and against a real Couchbase Server.
Stack
_mouprevious values describing one mutationSafeDocumentNametotesting/sgtestWriteUpdateXattrOnly_mouwhosepRevis a made-up zero🤖 Generated with Claude Code