@@ -78,6 +78,105 @@ func TestReplayController(t *testing.T) {
7878 require .Equal (t , expected , ops )
7979}
8080
81+ // Test that WithBackPressure skips backpressure entirely when ReplayMemoryCeiling is zero.
82+ func TestReplayControllerZeroCeiling (t * testing.T ) {
83+ var flushed bool
84+ flusher := newDumbFlusher (func () {
85+ flushed = true
86+ })
87+ rc := newReplayController (nilMetrics (), WALConfig {ReplayMemoryCeiling : 0 }, flusher )
88+
89+ // Add a large amount of data — with a zero ceiling this should not trigger flushing.
90+ rc .Add (1 << 30 ) // 1GB
91+
92+ err := rc .WithBackPressure (func () error {
93+ rc .Add (1 << 30 )
94+ return nil
95+ })
96+
97+ require .NoError (t , err )
98+ require .False (t , flushed , "flusher should not be called when ReplayMemoryCeiling is zero" )
99+ }
100+
101+ // Test that WithBackPressure returns an error when a flush makes no progress reducing memory.
102+ func TestReplayControllerNoProgressFlush (t * testing.T ) {
103+ flusher := newDumbFlusher (nil ) // no-op: never calls rc.Sub, so bytes never decrease
104+ rc := newReplayController (nilMetrics (), WALConfig {ReplayMemoryCeiling : 100 }, flusher )
105+
106+ // Exceed 90% threshold (ceiling = 90).
107+ rc .Add (95 )
108+
109+ err := rc .WithBackPressure (func () error {
110+ return nil
111+ })
112+
113+ require .Error (t , err )
114+ require .Contains (t , err .Error (), "WAL replay flush made no progress" )
115+ }
116+
117+ // Test that WithBackPressure returns an error when flush makes partial progress but then stalls,
118+ // i.e. subsequent flushes no longer reduce memory below the ceiling.
119+ func TestReplayControllerPartialProgressFlush (t * testing.T ) {
120+ var rc * replayController
121+ flushCount := 0
122+ flusher := newDumbFlusher (func () {
123+ flushCount ++
124+ if flushCount == 1 {
125+ // First flush makes partial progress: drops from 200 to 95, still above 90% ceiling.
126+ rc .Sub (105 )
127+ }
128+ // Subsequent flushes are no-ops: bytes stay at 95, no further progress.
129+ })
130+ rc = newReplayController (nilMetrics (), WALConfig {ReplayMemoryCeiling : 100 }, flusher )
131+
132+ rc .Add (200 ) // well above the 90-byte ceiling
133+
134+ err := rc .WithBackPressure (func () error {
135+ return nil
136+ })
137+
138+ require .Error (t , err )
139+ require .Contains (t , err .Error (), "WAL replay flush made no progress" )
140+ require .Equal (t , 2 , flushCount , "should have flushed twice: once with progress, once stalled" )
141+ }
142+
143+ // Test that WithBackPressure does not spuriously error when concurrent workers refill currentBytes
144+ // during a flush. The flush made real progress (Sub was called), so no error should be returned
145+ // even though currentBytes ends up higher than the pre-flush snapshot due to concurrent Add calls.
146+ //
147+ // Sequence:
148+ // 1. bytes=95 > ceiling=90, enter loop; old code snapshots before=95
149+ // 2. Flush runs: Sub(80) → bytes=15; concurrent goroutine adds 85 → bytes=100
150+ // 3. Old check: currentBytes(100) >= before(95) → spurious error
151+ // 4. New check: totalSubtracted increased → no error, loop continues and drains on next flush
152+ func TestReplayControllerNoSpuriousErrorOnConcurrentAdd (t * testing.T ) {
153+ var rc * replayController
154+ var once sync.Once
155+ flushed := make (chan struct {})
156+ flusher := newDumbFlusher (func () {
157+ rc .Sub (80 ) // genuine progress each flush
158+ once .Do (func () { close (flushed ) }) // signal the concurrent goroutine only on first flush
159+ })
160+ rc = newReplayController (nilMetrics (), WALConfig {ReplayMemoryCeiling : 100 }, flusher )
161+ rc .Add (95 ) // above 90% ceiling
162+
163+ var wg sync.WaitGroup
164+ wg .Add (1 )
165+ go func () {
166+ defer wg .Done ()
167+ // Simulate a concurrent worker adding bytes while the first flush is running,
168+ // pushing currentBytes back above the pre-flush snapshot (100 > before=95).
169+ // The loop will flush again and drain normally; no error should be returned.
170+ <- flushed
171+ rc .Add (85 ) // 15 + 85 = 100, above the original before=95
172+ }()
173+
174+ err := rc .WithBackPressure (func () error { return nil })
175+ wg .Wait ()
176+
177+ require .NoError (t , err , "concurrent Add during flush should not cause a spurious no-progress error" )
178+ }
179+
81180// Test to ensure only one flush happens at a time when multiple goroutines call WithBackPressure
82181func TestReplayControllerConcurrentFlushes (t * testing.T ) {
83182 t .Run ("multiple goroutines wait for single flush" , func (t * testing.T ) {
0 commit comments