@@ -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,28 @@ 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 (t * testing.T , truncator * WAGTruncator , expected uint64 ) {
122+ t .Helper ()
123+ testutils .SucceedsSoon (t , func () error {
124+ actual := truncator .lastTruncatedWAGIndex .Load ()
125+ if actual == expected {
126+ return nil
127+ }
128+ return errors .Newf ("expected lastTruncatedWAGIndex %d, got %d" , expected , actual )
129+ })
130+ }
131+
106132func TestTruncateApplied (t * testing.T ) {
107133 defer leaktest .AfterTest (t )()
108134 defer log .Scope (t ).Close (t )
@@ -240,7 +266,7 @@ func TestTruncateApplied(t *testing.T) {
240266 t .Run ("" , func (t * testing.T ) {
241267 e := makeTestEngines ()
242268 defer e .Close ()
243- truncator := NewWAGTruncator (st , e .Engines )
269+ truncator := NewWAGTruncator (st , e .Engines , & e . seq )
244270 tc .setup (t , & e )
245271 require .NoError (t , e .stateEngine .Flush ())
246272 require .NoError (t , truncator .TruncateAll (ctx ))
@@ -268,7 +294,7 @@ func TestTruncateAndClearRaftState(t *testing.T) {
268294 t .Run (eventType .String (), func (t * testing.T ) {
269295 e := makeTestEngines ()
270296 defer e .Close ()
271- truncator := NewWAGTruncator (st , e .Engines )
297+ truncator := NewWAGTruncator (st , e .Engines , & e . seq )
272298
273299 // Write WAG nodes: init then destroy/subsume at index 20.
274300 e .writeWAGNode (t , wagpb.Event {
@@ -372,7 +398,7 @@ func TestTruncateGapHandling(t *testing.T) {
372398 t .Run ("" , func (t * testing.T ) {
373399 e := makeTestEngines ()
374400 defer e .Close ()
375- truncator := NewWAGTruncator (st , e .Engines )
401+ truncator := NewWAGTruncator (st , e .Engines , & e . seq )
376402
377403 // Write WAG nodes at indices 2, 4, 6.
378404 e .seq .Next ()
@@ -412,3 +438,81 @@ func TestTruncateGapHandling(t *testing.T) {
412438 })
413439 }
414440}
441+
442+ // TestWAGTruncatorBackground verifies that the WAGTruncator background
443+ // goroutine only truncates WAG nodes when both conditions are met: (1) the
444+ // state engine has flushed, and (2) there are WAG nodes to truncate (i.e.,
445+ // seq.Load() > lastTruncatedWAGIndex).
446+ func TestWAGTruncatorBackground (t * testing.T ) {
447+ defer leaktest .AfterTest (t )()
448+ defer log .Scope (t ).Close (t )
449+ ctx := context .Background ()
450+ st := cluster .MakeTestingClusterSettings ()
451+ e := makeTestEngines ()
452+ defer e .Close ()
453+ stopper := stop .NewStopper ()
454+ defer stopper .Stop (ctx )
455+ r1 := roachpb.FullReplicaID {RangeID : 1 , ReplicaID : 1 }
456+ sl := MakeStateLoader (r1 .RangeID )
457+
458+ // Initialize replica state so events can be considered applied.
459+ require .NoError (t , sl .SetRaftReplicaID (ctx , e .StateEngine (), r1 .ReplicaID ))
460+ require .NoError (t , sl .SetRangeAppliedState (ctx , e .StateEngine (),
461+ & kvserverpb.RangeAppliedState {RaftAppliedIndex : 100 }))
462+
463+ // Start the periodic WAG truncation background task.
464+ truncator := NewWAGTruncator (st , e .Engines , & e .seq )
465+ require .NoError (t , truncator .Start (ctx , stopper ))
466+
467+ flushStateEngineAndSignal := func () {
468+ require .NoError (t , e .StateEngine ().Flush ())
469+ truncator .DurabilityAdvancedCallback ()
470+ }
471+
472+ // No WAG nodes exist. Flushing the state engine should not cause the
473+ // truncator to do anything (seq.Load() == lastTruncatedWAGIndex == 0).
474+ flushStateEngineAndSignal ()
475+ eventuallyExpectWAGNodesIndices (t , & e , nil )
476+ require .Equal (t , truncator .lastTruncatedWAGIndex .Load (), uint64 (0 ))
477+
478+ // Write two WAG nodes whose events are applied (index <= 100).
479+ e .writeWAGNode (t , wagpb.Event {
480+ Addr : wagpb .MakeAddr (r1 , 10 ), Type : wagpb .EventInit ,
481+ })
482+ e .writeWAGNode (t , wagpb.Event {
483+ Addr : wagpb .MakeAddr (r1 , 20 ), Type : wagpb .EventApply ,
484+ })
485+
486+ // WAG nodes exist, but the state engine hasn't flushed yet, so the
487+ // GuaranteedDurability reader won't see the replica state. Signal the
488+ // truncator without flushing first.
489+ truncator .DurabilityAdvancedCallback ()
490+ eventuallyExpectWAGNodesIndices (t , & e , []uint64 {1 , 2 })
491+ require .Equal (t , truncator .lastTruncatedWAGIndex .Load (), uint64 (0 ))
492+
493+ // Now flush the state engine and signal again. Both nodes should be
494+ // truncated since their events are applied (index 10 and 20 <= 100).
495+ flushStateEngineAndSignal ()
496+ eventuallyExpectWAGNodesIndices (t , & e , nil )
497+ // The lastTruncatedWAGIndex is stored after truncateAppliedNodes returns,
498+ // so there is a small window between the WAG nodes being deleted and the
499+ // index being updated. Use an eventually-consistent check.
500+ eventuallyExpectLastTruncatedWAGIndex (t , truncator , 2 )
501+
502+ // Write a third WAG node that is NOT applied (index 200 > 100).
503+ e .writeWAGNode (t , wagpb.Event {
504+ Addr : wagpb .MakeAddr (r1 , 200 ), Type : wagpb .EventApply ,
505+ })
506+ flushStateEngineAndSignal ()
507+ // Node 3 should remain because its event isn't applied yet.
508+ eventuallyExpectWAGNodesIndices (t , & e , []uint64 {3 })
509+ require .Equal (t , truncator .lastTruncatedWAGIndex .Load (), uint64 (2 ))
510+
511+ // Advance the applied index past 200 and flush. Now node 3 should be
512+ // truncated.
513+ require .NoError (t , sl .SetRangeAppliedState (ctx , e .StateEngine (),
514+ & kvserverpb.RangeAppliedState {RaftAppliedIndex : 200 }))
515+ flushStateEngineAndSignal ()
516+ eventuallyExpectWAGNodesIndices (t , & e , nil )
517+ eventuallyExpectLastTruncatedWAGIndex (t , truncator , 3 )
518+ }
0 commit comments