github.com/zuoyebang/bitalosdb@v1.1.1-0.20240516111551-79a8c4d8ce20/internal/cache/lrucache/clockpro_test.go (about)

     1  // Copyright 2018. All rights reserved. Use of this source code is governed by
     2  // an MIT-style license that can be found in the LICENSE file.
     3  
     4  package lrucache
     5  
     6  import (
     7  	"bufio"
     8  	"bytes"
     9  	"fmt"
    10  	"os"
    11  	"runtime"
    12  	"strconv"
    13  	"sync"
    14  	"testing"
    15  	"time"
    16  
    17  	"github.com/stretchr/testify/require"
    18  	"github.com/zuoyebang/bitalosdb/internal/options"
    19  	"golang.org/x/exp/rand"
    20  )
    21  
    22  func testNewShards() *LruCache {
    23  	opts := &options.CacheOptions{
    24  		Size:     100,
    25  		Shards:   1,
    26  		HashSize: 1024,
    27  	}
    28  	cache := newShards(opts)
    29  	return cache
    30  }
    31  
    32  func TestCache(t *testing.T) {
    33  	// Test data was generated from the python code
    34  	f, err := os.Open("testdata/cache")
    35  	require.NoError(t, err)
    36  
    37  	opts := &options.CacheOptions{
    38  		Size:     200,
    39  		Shards:   1,
    40  		HashSize: 1024,
    41  	}
    42  	cache := newShards(opts)
    43  	defer cache.Unref()
    44  
    45  	scanner := bufio.NewScanner(f)
    46  	line := 1
    47  
    48  	for scanner.Scan() {
    49  		fields := bytes.Fields(scanner.Bytes())
    50  
    51  		key, err := strconv.Atoi(string(fields[0]))
    52  		require.NoError(t, err)
    53  
    54  		wantHit := fields[1][0] == 'h'
    55  
    56  		var hit bool
    57  		h := cache.getValue(1, uint64(key))
    58  		if v := h.Get(); v == nil {
    59  			value := cache.Alloc(1)
    60  			value.Buf()[0] = fields[0][0]
    61  			cache.setValue(1, uint64(key), value).Release()
    62  		} else {
    63  			hit = true
    64  			if !bytes.Equal(v, fields[0][:1]) {
    65  				t.Errorf("%d: cache returned bad data: got %s , want %s\n", line, v, fields[0][:1])
    66  			}
    67  		}
    68  		h.Release()
    69  		if hit != wantHit {
    70  			t.Errorf("%d: cache hit mismatch: got %v, want %v\n", line, hit, wantHit)
    71  		}
    72  		line++
    73  	}
    74  }
    75  
    76  func testValue(cache *LruCache, s string, repeat int) *Value {
    77  	b := bytes.Repeat([]byte(s), repeat)
    78  	v := cache.Alloc(len(b))
    79  	copy(v.Buf(), b)
    80  	return v
    81  }
    82  
    83  func TestCacheDelete(t *testing.T) {
    84  	cache := testNewShards()
    85  	defer cache.Unref()
    86  
    87  	cache.setValue(1, 0, testValue(cache, "a", 5)).Release()
    88  	cache.setValue(1, 1, testValue(cache, "a", 5)).Release()
    89  	cache.setValue(1, 2, testValue(cache, "a", 5)).Release()
    90  	if expected, size := int64(15), cache.Size(); expected != size {
    91  		t.Fatalf("expected cache size %d, but found %d", expected, size)
    92  	}
    93  	cache.del(1, 1)
    94  	if expected, size := int64(10), cache.Size(); expected != size {
    95  		t.Fatalf("expected cache size %d, but found %d", expected, size)
    96  	}
    97  	if h := cache.getValue(1, 0); h.Get() == nil {
    98  		t.Fatalf("expected to find block 0/0")
    99  	} else {
   100  		h.Release()
   101  	}
   102  	if h := cache.getValue(1, 1); h.Get() != nil {
   103  		t.Fatalf("expected to not find block 1/0")
   104  	} else {
   105  		h.Release()
   106  	}
   107  	// Deleting a non-existing block does nothing.
   108  	cache.del(1, 1)
   109  	if expected, size := int64(10), cache.Size(); expected != size {
   110  		t.Fatalf("expected cache size %d, but found %d", expected, size)
   111  	}
   112  }
   113  
   114  func TestEvictAll(t *testing.T) {
   115  	// Verify that it is okay to evict all of the data from a cache. Previously
   116  	// this would trigger a nil-pointer dereference.
   117  	cache := testNewShards()
   118  	defer cache.Unref()
   119  
   120  	cache.setValue(1, 0, testValue(cache, "a", 101)).Release()
   121  	cache.setValue(1, 1, testValue(cache, "a", 101)).Release()
   122  }
   123  
   124  func TestMultipleDBs(t *testing.T) {
   125  	cache := testNewShards()
   126  	defer cache.Unref()
   127  
   128  	cache.setValue(1, 0, testValue(cache, "a", 5)).Release()
   129  	cache.setValue(2, 0, testValue(cache, "b", 5)).Release()
   130  	if expected, size := int64(10), cache.Size(); expected != size {
   131  		t.Fatalf("expected cache size %d, but found %d", expected, size)
   132  	}
   133  	h := cache.getValue(1, 0)
   134  	if v := h.Get(); string(v) != "aaaaa" {
   135  		t.Fatalf("expected aaaaa, but found %s", v)
   136  	}
   137  	h = cache.getValue(2, 0)
   138  	if v := h.Get(); string(v) != "bbbbb" {
   139  		t.Fatalf("expected bbbbb, but found %s", v)
   140  	} else {
   141  		h.Release()
   142  	}
   143  }
   144  
   145  func TestZeroSize(t *testing.T) {
   146  	opts := &options.CacheOptions{
   147  		Size:     0,
   148  		Shards:   1,
   149  		HashSize: 1024,
   150  	}
   151  	cache := newShards(opts)
   152  	defer cache.Unref()
   153  
   154  	cache.setValue(1, 0, testValue(cache, "a", 5)).Release()
   155  }
   156  
   157  func TestReserve(t *testing.T) {
   158  	opts := &options.CacheOptions{
   159  		Size:     4,
   160  		Shards:   2,
   161  		HashSize: 1024,
   162  	}
   163  	cache := newShards(opts)
   164  	defer cache.Unref()
   165  
   166  	cache.setValue(1, 1, testValue(cache, "a", 1)).Release()
   167  	cache.setValue(1, 2, testValue(cache, "a", 1)).Release()
   168  	require.EqualValues(t, 2, cache.Size())
   169  	r := cache.Reserve(1)
   170  	require.EqualValues(t, 0, cache.Size())
   171  	cache.setValue(1, 1, testValue(cache, "a", 1)).Release()
   172  	cache.setValue(1, 2, testValue(cache, "a", 1)).Release()
   173  	cache.setValue(1, 3, testValue(cache, "a", 1)).Release()
   174  	cache.setValue(1, 4, testValue(cache, "a", 1)).Release()
   175  	require.EqualValues(t, 2, cache.Size())
   176  	r()
   177  	require.EqualValues(t, 2, cache.Size())
   178  	cache.setValue(1, 1, testValue(cache, "a", 1)).Release()
   179  	cache.setValue(1, 2, testValue(cache, "a", 1)).Release()
   180  	require.EqualValues(t, 4, cache.Size())
   181  }
   182  
   183  func TestReserveDoubleRelease(t *testing.T) {
   184  	cache := testNewShards()
   185  	defer cache.Unref()
   186  
   187  	r := cache.Reserve(10)
   188  	r()
   189  
   190  	result := func() (result string) {
   191  		defer func() {
   192  			if v := recover(); v != nil {
   193  				result = fmt.Sprint(v)
   194  			}
   195  		}()
   196  		r()
   197  		return ""
   198  	}()
   199  	const expected = "cache: cache reservation already released"
   200  	if expected != result {
   201  		t.Fatalf("expected %q, but found %q", expected, result)
   202  	}
   203  }
   204  
   205  func TestCacheStressSetExisting(t *testing.T) {
   206  	opts := &options.CacheOptions{
   207  		Size:     1,
   208  		Shards:   1,
   209  		HashSize: 1024,
   210  	}
   211  	cache := newShards(opts)
   212  	defer cache.Unref()
   213  
   214  	var wg sync.WaitGroup
   215  	for i := 0; i < 10; i++ {
   216  		wg.Add(1)
   217  		go func(i int) {
   218  			defer wg.Done()
   219  			for j := 0; j < 10000; j++ {
   220  				cache.setValue(1, uint64(i), testValue(cache, "a", 1)).Release()
   221  				runtime.Gosched()
   222  			}
   223  		}(i)
   224  	}
   225  	wg.Wait()
   226  }
   227  
   228  func TestCacheSetGet(t *testing.T) {
   229  	const size = 100000
   230  
   231  	opts := &options.CacheOptions{
   232  		Size:     size,
   233  		Shards:   1,
   234  		HashSize: 1024,
   235  	}
   236  	cache := newShards(opts)
   237  	defer cache.Unref()
   238  
   239  	for i := 0; i < size; i++ {
   240  		v := testValue(cache, "a", 1)
   241  		cache.setValue(1, uint64(i), v).Release()
   242  	}
   243  
   244  	for i := 0; i < size; i++ {
   245  		h := cache.getValue(1, uint64(i))
   246  		if h.Get() == nil {
   247  			t.Fatalf("failed to lookup value key=%d", i)
   248  		}
   249  		h.Release()
   250  	}
   251  }
   252  
   253  func TestCacheSetNil(t *testing.T) {
   254  	const size = 100000
   255  
   256  	opts := &options.CacheOptions{
   257  		Size:     size,
   258  		Shards:   1,
   259  		HashSize: 1024,
   260  	}
   261  
   262  	cache := NewLrucache(opts)
   263  	defer cache.Unref()
   264  
   265  	k := []byte("test1")
   266  
   267  	require.NoError(t, cache.Set(k, nil, 0))
   268  	_, _, found := cache.Get(k, 0)
   269  	require.Equal(t, false, found)
   270  }
   271  
   272  func BenchmarkCacheGet(b *testing.B) {
   273  	const size = 100000
   274  
   275  	opts := &options.CacheOptions{
   276  		Size:     size,
   277  		Shards:   1,
   278  		HashSize: 1024,
   279  	}
   280  	cache := newShards(opts)
   281  	defer cache.Unref()
   282  
   283  	for i := 0; i < size; i++ {
   284  		v := testValue(cache, "a", 1)
   285  		cache.setValue(1, uint64(i), v).Release()
   286  	}
   287  
   288  	b.ResetTimer()
   289  	b.RunParallel(func(pb *testing.PB) {
   290  		rng := rand.New(rand.NewSource(uint64(time.Now().UnixNano())))
   291  
   292  		for pb.Next() {
   293  			h := cache.getValue(1, uint64(rng.Intn(size)))
   294  			if h.Get() == nil {
   295  				b.Fatal("failed to lookup value")
   296  			}
   297  			h.Release()
   298  		}
   299  	})
   300  }
   301  
   302  func TestReserveColdTarget(t *testing.T) {
   303  	// If coldTarget isn't updated when we call shard.Reserve,
   304  	// then we unnecessarily remove nodes from the
   305  	// cache.
   306  	cache := testNewShards()
   307  	defer cache.Unref()
   308  
   309  	for i := 0; i < 50; i++ {
   310  		cache.setValue(uint64(i+1), 0, testValue(cache, "a", 1)).Release()
   311  	}
   312  
   313  	if cache.Size() != 50 {
   314  		require.Equal(t, 50, cache.Size(), "nodes were unnecessarily evicted from the cache")
   315  	}
   316  
   317  	// There won't be enough space left for 50 nodes in the cache after
   318  	// we call shard.Reserve. This should trigger a call to evict.
   319  	cache.Reserve(51)
   320  
   321  	// If we don't update coldTarget in Reserve then the cache gets emptied to
   322  	// size 0. In shard.Evict, we loop until shard.Size() < shard.targetSize().
   323  	// Therefore, 100 - 51 = 49, but we evict one more node.
   324  	if cache.Size() != 48 {
   325  		t.Fatalf("expected positive cache size %d, but found %d", 48, cache.Size())
   326  	}
   327  }