Skip to content

Commit bb49c0c

Browse files
authored
CBG-5503: TestHLVVersionAheadOfCASCorrection windows flake (#8412)
1 parent 0a5a88d commit bb49c0c

1 file changed

Lines changed: 30 additions & 9 deletions

File tree

db/crud.go

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3221,11 +3221,18 @@ func (db *DatabaseCollectionWithUser) postWriteUpdateHLV(ctx context.Context, do
32213221
return doc
32223222
}
32233223

3224-
// maxVersionCASCorrectionWait bounds how long correctVersionAheadOfCAS will sleep waiting for the server
3225-
// clock to catch up to a generated version. A gap larger than this indicates larger clock skew between
3226-
// Sync Gateway and the server, which we log rather than stall a write on.
3224+
// maxVersionCASCorrectionWait bounds the version-ahead gap correctVersionAheadOfCAS will wait out for the
3225+
// server clock to catch up to a generated version. A gap larger than this indicates larger clock skew
3226+
// between Sync Gateway and the server, which we log rather than stall a write on. The actual sleep can be
3227+
// up to maxVersionCASCorrectionWait + versionCASCorrectionSlack, as the slack is added on top of the gap.
32273228
const maxVersionCASCorrectionWait = time.Second
32283229

3230+
// versionCASCorrectionSlack pads the catch-up sleep in correctVersionAheadOfCAS so the re-stamped CAS
3231+
// lands strictly past the generated version rather than exactly on a physical-tick boundary. It absorbs
3232+
// small drift between the monotonic clock (time.Sleep) and the wall clock (CAS generation) that would
3233+
// otherwise leave the CAS one tick short. Kept small so it adds negligible latency to a corrected write.
3234+
const versionCASCorrectionSlack = time.Millisecond
3235+
32293236
// correctVersionAheadOfCAS handles the rare case where the HLC-generated current version is greater than
32303237
// the CAS the server assigned to the write (the Sync Gateway clock ran ahead of the server's). goxdcr
32313238
// rejects a document whose cv.ver exceeds its CAS, so leaving it would cause this mutation to be
@@ -3255,8 +3262,23 @@ func (db *DatabaseCollectionWithUser) correctVersionAheadOfCAS(ctx context.Conte
32553262
return doc
32563263
}
32573264

3258-
// sleep until the server's CAS would have caught up to the generated version, then re-stamp the CAS on the document so cv.ver <= cas holds for XDCR replication
3259-
time.Sleep(time.Duration(gap))
3265+
// Sleep until the CAS-assigning clock has advanced far enough that a fresh CAS would reach the
3266+
// generated version, then re-stamp so cv.ver <= cas holds for XDCR.
3267+
//
3268+
// The catch depends on three clocks, not two. The version is stamped from Sync Gateway's HLC;
3269+
// the CAS is stamped from the HLC of whatever assigns it (a Couchbase Server node, or Rosmar under unit tests).
3270+
// Either way, the sleep is the third clock: time.Sleep waits on the monotonic clock, which is a separate time
3271+
// source from the wall clock used by CAS generation on Windows. This means sleeping for the physical gap may
3272+
// still leave the CAS just short of the next physical-tick boundary.
3273+
//
3274+
// That tiny shortfall is amplified because version and CAS are compared after being floored to
3275+
// physical ticks (the low HLCLogicalBits are cleared). The success margin is already sub-tick, so a
3276+
// CAS clock that lands even 1ns before the target tick boundary produces a re-stamped CAS a whole
3277+
// tick (~65us) below the version. This is most visible under Rosmar, where the version and CAS share
3278+
// the same in-process clock and so land right on the boundary. Padding the sleep past the boundary
3279+
// makes us overshoot it regardless of the drift; the resulting CAS is at most versionCASCorrectionSlack
3280+
// larger than strictly required, which is harmless.
3281+
time.Sleep(time.Duration(gap) + versionCASCorrectionSlack)
32603282

32613283
cas2, err := db.restampVersionCAS(ctx, key, doc, casOut)
32623284
if err != nil {
@@ -3274,10 +3296,9 @@ func (db *DatabaseCollectionWithUser) correctVersionAheadOfCAS(ctx context.Conte
32743296
doc.SyncData.Cas = base.CasToString(cas2)
32753297
db.dbStats().Database().HLVVersionCASRetryCount.Add(1)
32763298

3277-
// Verify the re-stamp actually resolved the invariant. It may not when the version was ahead only within
3278-
// the logical-counter bits (the physical gap, and so the sleep, was ~0 and the server advanced its CAS by
3279-
// only a logical tick). We deliberately do not retry; a later mutation will resolve it, but surface a
3280-
// warning so the (likely clock-skew/divergence) condition is visible.
3299+
// With the sleep padded past the tick boundary, a CAS still below the version indicates real clock
3300+
// skew/divergence rather than a rounding miss. We deliberately do not retry; a later mutation will
3301+
// resolve it, but surface a warning so the condition is visible.
32813302
if doc.HLV.Version > cas2 {
32823303
base.WarnfCtx(ctx, "Re-stamped CAS %d for doc %q is still below the generated version %d; not retrying - the document may be skipped by XDCR until a later mutation", cas2, base.UD(doc.ID), doc.HLV.Version)
32833304
return doc

0 commit comments

Comments
 (0)