@@ -8,6 +8,7 @@ package kvstorage
88import (
99 "context"
1010 "math"
11+ "slices"
1112 "testing"
1213
1314 "github.com/cockroachdb/cockroach/pkg/keys"
@@ -20,8 +21,11 @@ import (
2021 "github.com/cockroachdb/cockroach/pkg/settings/cluster"
2122 "github.com/cockroachdb/cockroach/pkg/storage"
2223 "github.com/cockroachdb/cockroach/pkg/storage/fs"
24+ "github.com/cockroachdb/cockroach/pkg/testutils"
2325 "github.com/cockroachdb/cockroach/pkg/util/leaktest"
2426 "github.com/cockroachdb/cockroach/pkg/util/log"
27+ "github.com/cockroachdb/cockroach/pkg/util/stop"
28+ "github.com/cockroachdb/errors"
2529 "github.com/stretchr/testify/require"
2630 "golang.org/x/time/rate"
2731)
@@ -103,6 +107,30 @@ func (e *testEngines) listWAGNodes(t *testing.T) []uint64 {
103107 return indices
104108}
105109
110+ func eventuallyExpectWAGNodesIndices (t * testing.T , e * testEngines , expected []uint64 ) {
111+ t .Helper ()
112+ testutils .SucceedsSoon (t , func () error {
113+ actual := e .listWAGNodes (t )
114+ if slices .Equal (expected , actual ) {
115+ return nil
116+ }
117+ return errors .Newf ("expected WAG nodes %v, got %v" , expected , actual )
118+ })
119+ }
120+
121+ func eventuallyExpectLastTruncatedWAGIndex (
122+ t * testing.T , truncator * WAGTruncator , expected uint64 ,
123+ ) {
124+ t .Helper ()
125+ testutils .SucceedsSoon (t , func () error {
126+ actual := truncator .lastTruncatedWAGIndex .Load ()
127+ if actual == expected {
128+ return nil
129+ }
130+ return errors .Newf ("expected lastTruncatedWAGIndex %d, got %d" , expected , actual )
131+ })
132+ }
133+
106134func TestTruncateApplied (t * testing.T ) {
107135 defer leaktest .AfterTest (t )()
108136 defer log .Scope (t ).Close (t )
@@ -240,7 +268,7 @@ func TestTruncateApplied(t *testing.T) {
240268 t .Run ("" , func (t * testing.T ) {
241269 e := makeTestEngines ()
242270 defer e .Close ()
243- truncator := NewWAGTruncator (st , e .Engines )
271+ truncator := NewWAGTruncator (st , e .Engines , & e . seq )
244272 tc .setup (t , & e )
245273 require .NoError (t , e .stateEngine .Flush ())
246274 require .NoError (t , truncator .TruncateAll (ctx ))
@@ -268,7 +296,7 @@ func TestTruncateAndClearRaftState(t *testing.T) {
268296 t .Run (eventType .String (), func (t * testing.T ) {
269297 e := makeTestEngines ()
270298 defer e .Close ()
271- truncator := NewWAGTruncator (st , e .Engines )
299+ truncator := NewWAGTruncator (st , e .Engines , & e . seq )
272300
273301 // Write WAG nodes: init then destroy/subsume at index 20.
274302 e .writeWAGNode (t , wagpb.Event {
@@ -372,7 +400,7 @@ func TestTruncateGapHandling(t *testing.T) {
372400 t .Run ("" , func (t * testing.T ) {
373401 e := makeTestEngines ()
374402 defer e .Close ()
375- truncator := NewWAGTruncator (st , e .Engines )
403+ truncator := NewWAGTruncator (st , e .Engines , & e . seq )
376404
377405 // Write WAG nodes at indices 2, 4, 6.
378406 e .seq .Next ()
@@ -412,3 +440,81 @@ func TestTruncateGapHandling(t *testing.T) {
412440 })
413441 }
414442}
443+
444+ // TestWAGTruncatorBackground verifies that the WAGTruncator background
445+ // goroutine only truncates WAG nodes when both conditions are met: (1) the
446+ // state engine has flushed, and (2) there are WAG nodes to truncate (i.e.,
447+ // seq.Load() > lastTruncatedWAGIndex).
448+ func TestWAGTruncatorBackground (t * testing.T ) {
449+ defer leaktest .AfterTest (t )()
450+ defer log .Scope (t ).Close (t )
451+ ctx := context .Background ()
452+ st := cluster .MakeTestingClusterSettings ()
453+ e := makeTestEngines ()
454+ defer e .Close ()
455+ stopper := stop .NewStopper ()
456+ defer stopper .Stop (ctx )
457+ r1 := roachpb.FullReplicaID {RangeID : 1 , ReplicaID : 1 }
458+ sl := MakeStateLoader (r1 .RangeID )
459+
460+ // Initialize replica state so events can be considered applied.
461+ require .NoError (t , sl .SetRaftReplicaID (ctx , e .StateEngine (), r1 .ReplicaID ))
462+ require .NoError (t , sl .SetRangeAppliedState (ctx , e .StateEngine (),
463+ & kvserverpb.RangeAppliedState {RaftAppliedIndex : 100 }))
464+
465+ // Start the periodic WAG truncation background task.
466+ truncator := NewWAGTruncator (st , e .Engines , & e .seq )
467+ require .NoError (t , truncator .Start (ctx , stopper ))
468+
469+ flushStateEngineAndSignal := func () {
470+ require .NoError (t , e .StateEngine ().Flush ())
471+ truncator .DurabilityAdvancedCallback ()
472+ }
473+
474+ // No WAG nodes exist. Flushing the state engine should not cause the
475+ // truncator to do anything (seq.Load() == lastTruncatedWAGIndex == 0).
476+ flushStateEngineAndSignal ()
477+ eventuallyExpectWAGNodesIndices (t , & e , nil )
478+ require .Equal (t , truncator .lastTruncatedWAGIndex .Load (), uint64 (0 ))
479+
480+ // Write two WAG nodes whose events are applied (index <= 100).
481+ e .writeWAGNode (t , wagpb.Event {
482+ Addr : wagpb .MakeAddr (r1 , 10 ), Type : wagpb .EventInit ,
483+ })
484+ e .writeWAGNode (t , wagpb.Event {
485+ Addr : wagpb .MakeAddr (r1 , 20 ), Type : wagpb .EventApply ,
486+ })
487+
488+ // WAG nodes exist, but the state engine hasn't flushed yet, so the
489+ // GuaranteedDurability reader won't see the replica state. Signal the
490+ // truncator without flushing first.
491+ truncator .DurabilityAdvancedCallback ()
492+ eventuallyExpectWAGNodesIndices (t , & e , []uint64 {1 , 2 })
493+ require .Equal (t , truncator .lastTruncatedWAGIndex .Load (), uint64 (0 ))
494+
495+ // Now flush the state engine and signal again. Both nodes should be
496+ // truncated since their events are applied (index 10 and 20 <= 100).
497+ flushStateEngineAndSignal ()
498+ eventuallyExpectWAGNodesIndices (t , & e , nil )
499+ // The lastTruncatedWAGIndex is stored after truncateAppliedNodes returns,
500+ // so there is a small window between the WAG nodes being deleted and the
501+ // index being updated. Use an eventually-consistent check.
502+ eventuallyExpectLastTruncatedWAGIndex (t , truncator , 2 )
503+
504+ // Write a third WAG node that is NOT applied (index 200 > 100).
505+ e .writeWAGNode (t , wagpb.Event {
506+ Addr : wagpb .MakeAddr (r1 , 200 ), Type : wagpb .EventApply ,
507+ })
508+ flushStateEngineAndSignal ()
509+ // Node 3 should remain because its event isn't applied yet.
510+ eventuallyExpectWAGNodesIndices (t , & e , []uint64 {3 })
511+ require .Equal (t , truncator .lastTruncatedWAGIndex .Load (), uint64 (2 ))
512+
513+ // Advance the applied index past 200 and flush. Now node 3 should be
514+ // truncated.
515+ require .NoError (t , sl .SetRangeAppliedState (ctx , e .StateEngine (),
516+ & kvserverpb.RangeAppliedState {RaftAppliedIndex : 200 }))
517+ flushStateEngineAndSignal ()
518+ eventuallyExpectWAGNodesIndices (t , & e , nil )
519+ eventuallyExpectLastTruncatedWAGIndex (t , truncator , 3 )
520+ }
0 commit comments