github.com/zuoyebang/bitalosdb@v1.1.1-0.20240516111551-79a8c4d8ce20/internal/record/log_writer_test.go (about) 1 // Copyright 2021 The Bitalosdb author(hustxrb@163.com) and other contributors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package record 16 17 import ( 18 "bytes" 19 "sync" 20 "sync/atomic" 21 "testing" 22 "time" 23 24 "github.com/zuoyebang/bitalosdb/internal/vfs" 25 26 "github.com/cockroachdb/errors" 27 "github.com/stretchr/testify/require" 28 ) 29 30 type syncErrorFile struct { 31 vfs.File 32 err error 33 } 34 35 func (f syncErrorFile) Sync() error { 36 return f.err 37 } 38 39 func TestSyncQueue(t *testing.T) { 40 var q syncQueue 41 var closed int32 42 43 var flusherWG sync.WaitGroup 44 flusherWG.Add(1) 45 go func() { 46 defer flusherWG.Done() 47 for { 48 if atomic.LoadInt32(&closed) == 1 { 49 return 50 } 51 head, tail := q.load() 52 q.pop(head, tail, nil) 53 } 54 }() 55 56 var commitMu sync.Mutex 57 var doneWG sync.WaitGroup 58 for i := 0; i < SyncConcurrency; i++ { 59 doneWG.Add(1) 60 go func(i int) { 61 defer doneWG.Done() 62 for j := 0; j < 1000; j++ { 63 wg := &sync.WaitGroup{} 64 wg.Add(1) 65 commitMu.Lock() 66 q.push(wg, new(error)) 67 commitMu.Unlock() 68 wg.Wait() 69 } 70 }(i) 71 } 72 doneWG.Wait() 73 74 atomic.StoreInt32(&closed, 1) 75 flusherWG.Wait() 76 } 77 78 func TestFlusherCond(t *testing.T) { 79 var mu sync.Mutex 80 var q syncQueue 81 var c flusherCond 82 var closed bool 83 84 c.init(&mu, &q) 85 86 var flusherWG sync.WaitGroup 87 flusherWG.Add(1) 88 go func() { 89 defer flusherWG.Done() 90 91 mu.Lock() 92 defer mu.Unlock() 93 94 for { 95 for { 96 if closed { 97 return 98 } 99 if !q.empty() { 100 break 101 } 102 c.Wait() 103 } 104 105 head, tail := q.load() 106 q.pop(head, tail, nil) 107 } 108 }() 109 110 var commitMu sync.Mutex 111 var doneWG sync.WaitGroup 112 for i := 0; i < 2; i++ { 113 doneWG.Add(1) 114 go func(i int) { 115 defer doneWG.Done() 116 for j := 0; j < 10000; j++ { 117 wg := &sync.WaitGroup{} 118 wg.Add(1) 119 commitMu.Lock() 120 q.push(wg, new(error)) 121 commitMu.Unlock() 122 c.Signal() 123 wg.Wait() 124 } 125 }(i) 126 } 127 doneWG.Wait() 128 129 mu.Lock() 130 closed = true 131 c.Signal() 132 mu.Unlock() 133 flusherWG.Wait() 134 } 135 136 func TestSyncError(t *testing.T) { 137 mem := vfs.NewMem() 138 f, err := mem.Create("log") 139 require.NoError(t, err) 140 141 injectedErr := errors.New("injected error") 142 w := NewLogWriter(syncErrorFile{f, injectedErr}, 0) 143 144 syncRecord := func() { 145 var syncErr error 146 var syncWG sync.WaitGroup 147 syncWG.Add(1) 148 _, err = w.SyncRecord([]byte("hello"), &syncWG, &syncErr) 149 require.NoError(t, err) 150 syncWG.Wait() 151 if injectedErr != syncErr { 152 t.Fatalf("unexpected %v but found %v", injectedErr, syncErr) 153 } 154 } 155 syncRecord() 156 syncRecord() 157 syncRecord() 158 } 159 160 type syncFile struct { 161 writePos int64 162 syncPos int64 163 } 164 165 func (f *syncFile) Write(buf []byte) (int, error) { 166 n := len(buf) 167 atomic.AddInt64(&f.writePos, int64(n)) 168 return n, nil 169 } 170 171 func (f *syncFile) Sync() error { 172 atomic.StoreInt64(&f.syncPos, atomic.LoadInt64(&f.writePos)) 173 return nil 174 } 175 176 func TestSyncRecord(t *testing.T) { 177 f := &syncFile{} 178 w := NewLogWriter(f, 0) 179 180 var syncErr error 181 for i := 0; i < 100000; i++ { 182 var syncWG sync.WaitGroup 183 syncWG.Add(1) 184 offset, err := w.SyncRecord([]byte("hello"), &syncWG, &syncErr) 185 require.NoError(t, err) 186 syncWG.Wait() 187 require.NoError(t, syncErr) 188 if v := atomic.LoadInt64(&f.writePos); offset != v { 189 t.Fatalf("expected write pos %d, but found %d", offset, v) 190 } 191 if v := atomic.LoadInt64(&f.syncPos); offset != v { 192 t.Fatalf("expected sync pos %d, but found %d", offset, v) 193 } 194 } 195 } 196 197 type fakeTimer struct { 198 f func() 199 } 200 201 func (t *fakeTimer) Reset(d time.Duration) bool { 202 return false 203 } 204 205 func (t *fakeTimer) Stop() bool { 206 return false 207 } 208 209 func try(initialSleep, maxTotalSleep time.Duration, f func() error) error { 210 totalSleep := time.Duration(0) 211 for d := initialSleep; ; d *= 2 { 212 time.Sleep(d) 213 totalSleep += d 214 if err := f(); err == nil || totalSleep >= maxTotalSleep { 215 return err 216 } 217 } 218 } 219 220 func TestMinSyncInterval(t *testing.T) { 221 const minSyncInterval = 100 * time.Millisecond 222 223 f := &syncFile{} 224 w := NewLogWriter(f, 0) 225 w.SetMinSyncInterval(func() time.Duration { 226 return minSyncInterval 227 }) 228 229 var timer fakeTimer 230 w.afterFunc = func(d time.Duration, f func()) syncTimer { 231 if d != minSyncInterval { 232 t.Fatalf("expected minSyncInterval %s, but found %s", minSyncInterval, d) 233 } 234 timer.f = f 235 timer.Reset(d) 236 return &timer 237 } 238 239 syncRecord := func(n int) *sync.WaitGroup { 240 wg := &sync.WaitGroup{} 241 wg.Add(1) 242 _, err := w.SyncRecord(bytes.Repeat([]byte{'a'}, n), wg, new(error)) 243 require.NoError(t, err) 244 return wg 245 } 246 247 syncRecord(1).Wait() 248 249 startWritePos := atomic.LoadInt64(&f.writePos) 250 startSyncPos := atomic.LoadInt64(&f.syncPos) 251 252 var wg *sync.WaitGroup 253 for i := 0; i < 100; i++ { 254 wg = syncRecord(10000) 255 if v := atomic.LoadInt64(&f.syncPos); startSyncPos != v { 256 t.Fatalf("expected syncPos %d, but found %d", startSyncPos, v) 257 } 258 head, tail := w.flusher.syncQ.unpack(atomic.LoadUint64(&w.flusher.syncQ.headTail)) 259 waiters := head - tail 260 if waiters != uint32(i+1) { 261 t.Fatalf("expected %d waiters, but found %d", i+1, waiters) 262 } 263 } 264 265 err := try(time.Millisecond, 5*time.Second, func() error { 266 v := atomic.LoadInt64(&f.writePos) 267 if v > startWritePos { 268 return nil 269 } 270 return errors.Errorf("expected writePos > %d, but found %d", startWritePos, v) 271 }) 272 require.NoError(t, err) 273 274 timer.f() 275 wg.Wait() 276 277 if w, s := atomic.LoadInt64(&f.writePos), atomic.LoadInt64(&f.syncPos); w != s { 278 t.Fatalf("expected syncPos %d, but found %d", s, w) 279 } 280 } 281 282 func TestMinSyncIntervalClose(t *testing.T) { 283 const minSyncInterval = 100 * time.Millisecond 284 285 f := &syncFile{} 286 w := NewLogWriter(f, 0) 287 w.SetMinSyncInterval(func() time.Duration { 288 return minSyncInterval 289 }) 290 291 var timer fakeTimer 292 w.afterFunc = func(d time.Duration, f func()) syncTimer { 293 if d != minSyncInterval { 294 t.Fatalf("expected minSyncInterval %s, but found %s", minSyncInterval, d) 295 } 296 timer.f = f 297 timer.Reset(d) 298 return &timer 299 } 300 301 syncRecord := func(n int) *sync.WaitGroup { 302 wg := &sync.WaitGroup{} 303 wg.Add(1) 304 _, err := w.SyncRecord(bytes.Repeat([]byte{'a'}, n), wg, new(error)) 305 require.NoError(t, err) 306 return wg 307 } 308 309 syncRecord(1).Wait() 310 311 wg := syncRecord(1) 312 require.NoError(t, w.Close()) 313 wg.Wait() 314 }