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  }