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  }