github.com/zuoyebang/bitalostable@v1.0.1-0.20240229032404-e3b99a834294/record/log_writer_test.go (about) 1 // Copyright 2019 The LevelDB-Go and Pebble Authors. All rights reserved. Use 2 // of this source code is governed by a BSD-style license that can be found in 3 // the LICENSE file. 4 5 package record 6 7 import ( 8 "bytes" 9 "sync" 10 "sync/atomic" 11 "testing" 12 "time" 13 14 "github.com/cockroachdb/errors" 15 "github.com/stretchr/testify/require" 16 "github.com/zuoyebang/bitalostable/vfs" 17 ) 18 19 type syncErrorFile struct { 20 vfs.File 21 err error 22 } 23 24 func (f syncErrorFile) Sync() error { 25 return f.err 26 } 27 28 func TestSyncQueue(t *testing.T) { 29 var q syncQueue 30 var closed int32 31 32 var flusherWG sync.WaitGroup 33 flusherWG.Add(1) 34 go func() { 35 defer flusherWG.Done() 36 for { 37 if atomic.LoadInt32(&closed) == 1 { 38 return 39 } 40 head, tail, _ := q.load() 41 q.pop(head, tail, nil) 42 } 43 }() 44 45 var commitMu sync.Mutex 46 var doneWG sync.WaitGroup 47 for i := 0; i < SyncConcurrency; i++ { 48 doneWG.Add(1) 49 go func(i int) { 50 defer doneWG.Done() 51 for j := 0; j < 1000; j++ { 52 wg := &sync.WaitGroup{} 53 wg.Add(1) 54 // syncQueue is a single-producer, single-consumer queue. We need to 55 // provide mutual exclusion on the producer side. 56 commitMu.Lock() 57 q.push(wg, new(error)) 58 commitMu.Unlock() 59 wg.Wait() 60 } 61 }(i) 62 } 63 doneWG.Wait() 64 65 atomic.StoreInt32(&closed, 1) 66 flusherWG.Wait() 67 } 68 69 func TestFlusherCond(t *testing.T) { 70 var mu sync.Mutex 71 var q syncQueue 72 var c flusherCond 73 var closed bool 74 75 c.init(&mu, &q) 76 77 var flusherWG sync.WaitGroup 78 flusherWG.Add(1) 79 go func() { 80 defer flusherWG.Done() 81 82 mu.Lock() 83 defer mu.Unlock() 84 85 for { 86 for { 87 if closed { 88 return 89 } 90 if !q.empty() { 91 break 92 } 93 c.Wait() 94 } 95 96 head, tail, _ := q.load() 97 q.pop(head, tail, nil) 98 } 99 }() 100 101 var commitMu sync.Mutex 102 var doneWG sync.WaitGroup 103 // NB: we're testing with low concurrency here, because what we want to 104 // stress is that signalling of the flusherCond works 105 // correctly. Specifically, we want to make sure that a signal is "lost", 106 // causing the test to wedge. 107 for i := 0; i < 2; i++ { 108 doneWG.Add(1) 109 go func(i int) { 110 defer doneWG.Done() 111 for j := 0; j < 10000; j++ { 112 wg := &sync.WaitGroup{} 113 wg.Add(1) 114 // syncQueue is a single-producer, single-consumer queue. We need to 115 // provide mutual exclusion on the producer side. 116 commitMu.Lock() 117 q.push(wg, new(error)) 118 commitMu.Unlock() 119 c.Signal() 120 wg.Wait() 121 } 122 }(i) 123 } 124 doneWG.Wait() 125 126 mu.Lock() 127 closed = true 128 c.Signal() 129 mu.Unlock() 130 flusherWG.Wait() 131 } 132 133 func TestSyncError(t *testing.T) { 134 mem := vfs.NewMem() 135 f, err := mem.Create("log") 136 require.NoError(t, err) 137 138 injectedErr := errors.New("injected error") 139 w := NewLogWriter(syncErrorFile{f, injectedErr}, 0) 140 141 syncRecord := func() { 142 var syncErr error 143 var syncWG sync.WaitGroup 144 syncWG.Add(1) 145 _, err = w.SyncRecord([]byte("hello"), &syncWG, &syncErr) 146 require.NoError(t, err) 147 syncWG.Wait() 148 if injectedErr != syncErr { 149 t.Fatalf("unexpected %v but found %v", injectedErr, syncErr) 150 } 151 } 152 // First waiter receives error. 153 syncRecord() 154 // All subsequent waiters also receive the error. 155 syncRecord() 156 syncRecord() 157 } 158 159 type syncFile struct { 160 writePos int64 161 syncPos int64 162 } 163 164 func (f *syncFile) Write(buf []byte) (int, error) { 165 n := len(buf) 166 atomic.AddInt64(&f.writePos, int64(n)) 167 return n, nil 168 } 169 170 func (f *syncFile) Sync() error { 171 atomic.StoreInt64(&f.syncPos, atomic.LoadInt64(&f.writePos)) 172 return nil 173 } 174 175 func TestSyncRecord(t *testing.T) { 176 f := &syncFile{} 177 w := NewLogWriter(f, 0) 178 179 var syncErr error 180 for i := 0; i < 100000; i++ { 181 var syncWG sync.WaitGroup 182 syncWG.Add(1) 183 offset, err := w.SyncRecord([]byte("hello"), &syncWG, &syncErr) 184 require.NoError(t, err) 185 syncWG.Wait() 186 require.NoError(t, syncErr) 187 if v := atomic.LoadInt64(&f.writePos); offset != v { 188 t.Fatalf("expected write pos %d, but found %d", offset, v) 189 } 190 if v := atomic.LoadInt64(&f.syncPos); offset != v { 191 t.Fatalf("expected sync pos %d, but found %d", offset, v) 192 } 193 } 194 } 195 196 type fakeTimer struct { 197 f func() 198 } 199 200 func (t *fakeTimer) Reset(d time.Duration) bool { 201 return false 202 } 203 204 func (t *fakeTimer) Stop() bool { 205 return false 206 } 207 208 func try(initialSleep, maxTotalSleep time.Duration, f func() error) error { 209 totalSleep := time.Duration(0) 210 for d := initialSleep; ; d *= 2 { 211 time.Sleep(d) 212 totalSleep += d 213 if err := f(); err == nil || totalSleep >= maxTotalSleep { 214 return err 215 } 216 } 217 } 218 219 func TestMinSyncInterval(t *testing.T) { 220 const minSyncInterval = 100 * time.Millisecond 221 222 f := &syncFile{} 223 w := NewLogWriter(f, 0) 224 w.SetMinSyncInterval(func() time.Duration { 225 return minSyncInterval 226 }) 227 228 var timer fakeTimer 229 w.afterFunc = func(d time.Duration, f func()) syncTimer { 230 if d != minSyncInterval { 231 t.Fatalf("expected minSyncInterval %s, but found %s", minSyncInterval, d) 232 } 233 timer.f = f 234 timer.Reset(d) 235 return &timer 236 } 237 238 syncRecord := func(n int) *sync.WaitGroup { 239 wg := &sync.WaitGroup{} 240 wg.Add(1) 241 _, err := w.SyncRecord(bytes.Repeat([]byte{'a'}, n), wg, new(error)) 242 require.NoError(t, err) 243 return wg 244 } 245 246 // Sync one record which will cause the sync timer to kick in. 247 syncRecord(1).Wait() 248 249 startWritePos := atomic.LoadInt64(&f.writePos) 250 startSyncPos := atomic.LoadInt64(&f.syncPos) 251 252 // Write a bunch of large records. The sync position should not change 253 // because we haven't triggered the timer. But note that the writes should 254 // not block either even though syncing isn't being done. 255 var wg *sync.WaitGroup 256 for i := 0; i < 100; i++ { 257 wg = syncRecord(10000) 258 if v := atomic.LoadInt64(&f.syncPos); startSyncPos != v { 259 t.Fatalf("expected syncPos %d, but found %d", startSyncPos, v) 260 } 261 // NB: we can't use syncQueue.load() here as that will return 0,0 while the 262 // syncQueue is blocked. 263 head, tail := w.flusher.syncQ.unpack(atomic.LoadUint64(&w.flusher.syncQ.headTail)) 264 waiters := head - tail 265 if waiters != uint32(i+1) { 266 t.Fatalf("expected %d waiters, but found %d", i+1, waiters) 267 } 268 } 269 270 err := try(time.Millisecond, 5*time.Second, func() error { 271 v := atomic.LoadInt64(&f.writePos) 272 if v > startWritePos { 273 return nil 274 } 275 return errors.Errorf("expected writePos > %d, but found %d", startWritePos, v) 276 }) 277 require.NoError(t, err) 278 279 // Fire the timer, and then wait for the last record to sync. 280 timer.f() 281 wg.Wait() 282 283 if w, s := atomic.LoadInt64(&f.writePos), atomic.LoadInt64(&f.syncPos); w != s { 284 t.Fatalf("expected syncPos %d, but found %d", s, w) 285 } 286 } 287 288 func TestMinSyncIntervalClose(t *testing.T) { 289 const minSyncInterval = 100 * time.Millisecond 290 291 f := &syncFile{} 292 w := NewLogWriter(f, 0) 293 w.SetMinSyncInterval(func() time.Duration { 294 return minSyncInterval 295 }) 296 297 var timer fakeTimer 298 w.afterFunc = func(d time.Duration, f func()) syncTimer { 299 if d != minSyncInterval { 300 t.Fatalf("expected minSyncInterval %s, but found %s", minSyncInterval, d) 301 } 302 timer.f = f 303 timer.Reset(d) 304 return &timer 305 } 306 307 syncRecord := func(n int) *sync.WaitGroup { 308 wg := &sync.WaitGroup{} 309 wg.Add(1) 310 _, err := w.SyncRecord(bytes.Repeat([]byte{'a'}, n), wg, new(error)) 311 require.NoError(t, err) 312 return wg 313 } 314 315 // Sync one record which will cause the sync timer to kick in. 316 syncRecord(1).Wait() 317 318 // Syncing another record will not complete until the timer is fired OR the 319 // writer is closed. 320 wg := syncRecord(1) 321 require.NoError(t, w.Close()) 322 wg.Wait() 323 } 324 325 type syncFileWithWait struct { 326 f syncFile 327 writeWG sync.WaitGroup 328 syncWG sync.WaitGroup 329 } 330 331 func (f *syncFileWithWait) Write(buf []byte) (int, error) { 332 f.writeWG.Wait() 333 return f.f.Write(buf) 334 } 335 336 func (f *syncFileWithWait) Sync() error { 337 f.syncWG.Wait() 338 return f.f.Sync() 339 } 340 341 func TestMetricsWithoutSync(t *testing.T) { 342 f := &syncFileWithWait{} 343 f.writeWG.Add(1) 344 w := NewLogWriter(f, 0) 345 offset, err := w.SyncRecord([]byte("hello"), nil, nil) 346 require.NoError(t, err) 347 const recordSize = 16 348 require.EqualValues(t, recordSize, offset) 349 // We have 512KB of buffer capacity, and 5 bytes + overhead = 16 bytes for 350 // each record. Write 28 * 1024 records to fill it up to 87.5%. This 351 // constitutes ~14 blocks (each 32KB). 352 const numRecords = 28 << 10 353 for i := 0; i < numRecords; i++ { 354 _, err = w.SyncRecord([]byte("hello"), nil, nil) 355 require.NoError(t, err) 356 } 357 // Unblock the flush loop. It will run once or twice to write these blocks, 358 // plus may run one more time due to the Close, so up to 3 runs. So ~14 359 // blocks flushed over up to 3 runs. 360 f.writeWG.Done() 361 w.Close() 362 m := w.Metrics() 363 // Mean is >= 4 filled blocks. 364 require.LessOrEqual(t, float64(4), m.PendingBufferLen.Mean()) 365 // None of these writes asked to be synced. 366 require.EqualValues(t, 0, int(m.SyncQueueLen.Mean())) 367 require.Less(t, int64(numRecords*recordSize), m.WriteThroughput.Bytes) 368 } 369 370 func TestMetricsWithSync(t *testing.T) { 371 f := &syncFileWithWait{} 372 f.syncWG.Add(1) 373 w := NewLogWriter(f, 0) 374 var wg sync.WaitGroup 375 wg.Add(100) 376 for i := 0; i < 100; i++ { 377 var syncErr error 378 _, err := w.SyncRecord([]byte("hello"), &wg, &syncErr) 379 require.NoError(t, err) 380 } 381 // Unblock the flush loop. It may have run once or twice for these writes, 382 // plus may run one more time due to the Close, so up to 3 runs. So 100 383 // elements in the sync queue, spread over up to 3 runs. 384 syncLatency := 10 * time.Millisecond 385 time.Sleep(syncLatency) 386 f.syncWG.Done() 387 w.Close() 388 m := w.Metrics() 389 require.LessOrEqual(t, float64(30), m.SyncQueueLen.Mean()) 390 // Allow for some inaccuracy in sleep and for two syncs, one of which was 391 // fast. 392 require.LessOrEqual(t, int64(syncLatency/(2*time.Microsecond)), 393 m.SyncLatencyMicros.ValueAtQuantile(90)) 394 require.LessOrEqual(t, int64(syncLatency/2), int64(m.WriteThroughput.WorkDuration)) 395 }