github.com/bhojpur/cache@v0.0.4/pkg/engine/ristretto/cache_test.go (about)

     1  package ristretto
     2  
     3  // Copyright (c) 2018 Bhojpur Consulting Private Limited, India. All rights reserved.
     4  
     5  // Permission is hereby granted, free of charge, to any person obtaining a copy
     6  // of this software and associated documentation files (the "Software"), to deal
     7  // in the Software without restriction, including without limitation the rights
     8  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     9  // copies of the Software, and to permit persons to whom the Software is
    10  // furnished to do so, subject to the following conditions:
    11  
    12  // The above copyright notice and this permission notice shall be included in
    13  // all copies or substantial portions of the Software.
    14  
    15  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    16  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    17  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    18  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    19  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    20  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    21  // THE SOFTWARE.
    22  
    23  import (
    24  	"fmt"
    25  	"math/rand"
    26  	"strconv"
    27  	"strings"
    28  	"sync"
    29  	"testing"
    30  	"time"
    31  
    32  	"github.com/stretchr/testify/require"
    33  )
    34  
    35  var wait = time.Millisecond * 10
    36  
    37  func TestCacheKeyToHash(t *testing.T) {
    38  	keyToHashCount := 0
    39  	c, err := NewCache(&Config{
    40  		NumCounters:        10,
    41  		MaxCost:            1000,
    42  		BufferItems:        64,
    43  		IgnoreInternalCost: true,
    44  		KeyToHash: func(key string) (uint64, uint64) {
    45  			keyToHashCount++
    46  			return defaultStringHash(key)
    47  		},
    48  	})
    49  	require.NoError(t, err)
    50  	if c.SetWithCost("1", 1, 1) {
    51  		time.Sleep(wait)
    52  		val, ok := c.Get("1")
    53  		require.True(t, ok)
    54  		require.NotNil(t, val)
    55  		c.Delete("1")
    56  	}
    57  	require.Equal(t, 3, keyToHashCount)
    58  }
    59  
    60  func TestCacheMaxCost(t *testing.T) {
    61  	charset := "abcdefghijklmnopqrstuvwxyz0123456789"
    62  	key := func() string {
    63  		k := make([]byte, 2)
    64  		for i := range k {
    65  			k[i] = charset[rand.Intn(len(charset))]
    66  		}
    67  		return string(k)
    68  	}
    69  	c, err := NewCache(&Config{
    70  		NumCounters: 12960, // 36^2 * 10
    71  		MaxCost:     1e6,   // 1mb
    72  		BufferItems: 64,
    73  		Metrics:     true,
    74  	})
    75  	require.NoError(t, err)
    76  	stop := make(chan struct{}, 8)
    77  	for i := 0; i < 8; i++ {
    78  		go func() {
    79  			for {
    80  				select {
    81  				case <-stop:
    82  					return
    83  				default:
    84  					time.Sleep(time.Millisecond)
    85  
    86  					k := key()
    87  					if _, ok := c.Get(k); !ok {
    88  						val := ""
    89  						if rand.Intn(100) < 10 {
    90  							val = "test"
    91  						} else {
    92  							val = strings.Repeat("a", 1000)
    93  						}
    94  						c.SetWithCost(key(), val, int64(2+len(val)))
    95  					}
    96  				}
    97  			}
    98  		}()
    99  	}
   100  	for i := 0; i < 20; i++ {
   101  		time.Sleep(time.Second)
   102  		cacheCost := c.Metrics.CostAdded() - c.Metrics.CostEvicted()
   103  		t.Logf("total cache cost: %d\n", cacheCost)
   104  		require.True(t, float64(cacheCost) <= float64(1e6*1.05))
   105  	}
   106  	for i := 0; i < 8; i++ {
   107  		stop <- struct{}{}
   108  	}
   109  }
   110  
   111  func TestUpdateMaxCost(t *testing.T) {
   112  	c, err := NewCache(&Config{
   113  		NumCounters: 10,
   114  		MaxCost:     10,
   115  		BufferItems: 64,
   116  	})
   117  	require.NoError(t, err)
   118  	require.Equal(t, int64(10), c.MaxCapacity())
   119  	require.True(t, c.SetWithCost("1", 1, 1))
   120  	time.Sleep(wait)
   121  	_, ok := c.Get("1")
   122  	// Set is rejected because the cost of the entry is too high
   123  	// when accounting for the internal cost of storing the entry.
   124  	require.False(t, ok)
   125  
   126  	// Update the max cost of the cache and retry.
   127  	c.SetCapacity(1000)
   128  	require.Equal(t, int64(1000), c.MaxCapacity())
   129  	require.True(t, c.SetWithCost("1", 1, 1))
   130  	time.Sleep(wait)
   131  	val, ok := c.Get("1")
   132  	require.True(t, ok)
   133  	require.NotNil(t, val)
   134  	c.Delete("1")
   135  }
   136  
   137  func TestNewCache(t *testing.T) {
   138  	_, err := NewCache(&Config{
   139  		NumCounters: 0,
   140  	})
   141  	require.Error(t, err)
   142  
   143  	_, err = NewCache(&Config{
   144  		NumCounters: 100,
   145  		MaxCost:     0,
   146  	})
   147  	require.Error(t, err)
   148  
   149  	_, err = NewCache(&Config{
   150  		NumCounters: 100,
   151  		MaxCost:     10,
   152  		BufferItems: 0,
   153  	})
   154  	require.Error(t, err)
   155  
   156  	c, err := NewCache(&Config{
   157  		NumCounters: 100,
   158  		MaxCost:     10,
   159  		BufferItems: 64,
   160  		Metrics:     true,
   161  	})
   162  	require.NoError(t, err)
   163  	require.NotNil(t, c)
   164  }
   165  
   166  func TestNilCache(t *testing.T) {
   167  	var c *Cache
   168  	val, ok := c.Get("1")
   169  	require.False(t, ok)
   170  	require.Nil(t, val)
   171  
   172  	require.False(t, c.SetWithCost("1", 1, 1))
   173  	c.Delete("1")
   174  	c.Clear()
   175  	c.Close()
   176  }
   177  
   178  func TestMultipleClose(t *testing.T) {
   179  	var c *Cache
   180  	c.Close()
   181  
   182  	var err error
   183  	c, err = NewCache(&Config{
   184  		NumCounters: 100,
   185  		MaxCost:     10,
   186  		BufferItems: 64,
   187  		Metrics:     true,
   188  	})
   189  	require.NoError(t, err)
   190  	c.Close()
   191  	c.Close()
   192  }
   193  
   194  func TestSetAfterClose(t *testing.T) {
   195  	c, err := newTestCache()
   196  	require.NoError(t, err)
   197  	require.NotNil(t, c)
   198  
   199  	c.Close()
   200  	require.False(t, c.SetWithCost("1", 1, 1))
   201  }
   202  
   203  func TestClearAfterClose(t *testing.T) {
   204  	c, err := newTestCache()
   205  	require.NoError(t, err)
   206  	require.NotNil(t, c)
   207  
   208  	c.Close()
   209  	c.Clear()
   210  }
   211  
   212  func TestGetAfterClose(t *testing.T) {
   213  	c, err := newTestCache()
   214  	require.NoError(t, err)
   215  	require.NotNil(t, c)
   216  
   217  	require.True(t, c.SetWithCost("1", 1, 1))
   218  	c.Close()
   219  
   220  	_, ok := c.Get("2")
   221  	require.False(t, ok)
   222  }
   223  
   224  func TestDelAfterClose(t *testing.T) {
   225  	c, err := newTestCache()
   226  	require.NoError(t, err)
   227  	require.NotNil(t, c)
   228  
   229  	require.True(t, c.SetWithCost("1", 1, 1))
   230  	c.Close()
   231  
   232  	c.Delete("1")
   233  }
   234  
   235  func TestCacheProcessItems(t *testing.T) {
   236  	m := &sync.Mutex{}
   237  	evicted := make(map[uint64]struct{})
   238  	c, err := NewCache(&Config{
   239  		NumCounters:        100,
   240  		MaxCost:            10,
   241  		BufferItems:        64,
   242  		IgnoreInternalCost: true,
   243  		Cost: func(value interface{}) int64 {
   244  			return int64(value.(int))
   245  		},
   246  		OnEvict: func(item *Item) {
   247  			m.Lock()
   248  			defer m.Unlock()
   249  			evicted[item.Key] = struct{}{}
   250  		},
   251  	})
   252  	require.NoError(t, err)
   253  
   254  	var key uint64
   255  	var conflict uint64
   256  
   257  	key, conflict = defaultStringHash("1")
   258  	c.setBuf <- &Item{
   259  		flag:     itemNew,
   260  		Key:      key,
   261  		Conflict: conflict,
   262  		Value:    1,
   263  		Cost:     0,
   264  	}
   265  	time.Sleep(wait)
   266  	require.True(t, c.policy.Has(key))
   267  	require.Equal(t, int64(1), c.policy.Cost(key))
   268  
   269  	key, conflict = defaultStringHash("1")
   270  	c.setBuf <- &Item{
   271  		flag:     itemUpdate,
   272  		Key:      key,
   273  		Conflict: conflict,
   274  		Value:    2,
   275  		Cost:     0,
   276  	}
   277  	time.Sleep(wait)
   278  	require.Equal(t, int64(2), c.policy.Cost(key))
   279  
   280  	key, conflict = defaultStringHash("1")
   281  	c.setBuf <- &Item{
   282  		flag:     itemDelete,
   283  		Key:      key,
   284  		Conflict: conflict,
   285  	}
   286  	time.Sleep(wait)
   287  	key, conflict = defaultStringHash("1")
   288  	val, ok := c.store.Get(key, conflict)
   289  	require.False(t, ok)
   290  	require.Nil(t, val)
   291  	require.False(t, c.policy.Has(1))
   292  
   293  	key, conflict = defaultStringHash("2")
   294  	c.setBuf <- &Item{
   295  		flag:     itemNew,
   296  		Key:      key,
   297  		Conflict: conflict,
   298  		Value:    2,
   299  		Cost:     3,
   300  	}
   301  	key, conflict = defaultStringHash("3")
   302  	c.setBuf <- &Item{
   303  		flag:     itemNew,
   304  		Key:      key,
   305  		Conflict: conflict,
   306  		Value:    3,
   307  		Cost:     3,
   308  	}
   309  	key, conflict = defaultStringHash("4")
   310  	c.setBuf <- &Item{
   311  		flag:     itemNew,
   312  		Key:      key,
   313  		Conflict: conflict,
   314  		Value:    3,
   315  		Cost:     3,
   316  	}
   317  	key, conflict = defaultStringHash("5")
   318  	c.setBuf <- &Item{
   319  		flag:     itemNew,
   320  		Key:      key,
   321  		Conflict: conflict,
   322  		Value:    3,
   323  		Cost:     5,
   324  	}
   325  	time.Sleep(wait)
   326  	m.Lock()
   327  	require.NotEqual(t, 0, len(evicted))
   328  	m.Unlock()
   329  
   330  	defer func() {
   331  		require.NotNil(t, recover())
   332  	}()
   333  	c.Close()
   334  	c.setBuf <- &Item{flag: itemNew}
   335  }
   336  
   337  func TestCacheGet(t *testing.T) {
   338  	c, err := NewCache(&Config{
   339  		NumCounters:        100,
   340  		MaxCost:            10,
   341  		BufferItems:        64,
   342  		IgnoreInternalCost: true,
   343  		Metrics:            true,
   344  	})
   345  	require.NoError(t, err)
   346  
   347  	key, conflict := defaultStringHash("1")
   348  	i := Item{
   349  		Key:      key,
   350  		Conflict: conflict,
   351  		Value:    1,
   352  	}
   353  	c.store.Set(&i)
   354  	val, ok := c.Get("1")
   355  	require.True(t, ok)
   356  	require.NotNil(t, val)
   357  
   358  	val, ok = c.Get("2")
   359  	require.False(t, ok)
   360  	require.Nil(t, val)
   361  
   362  	// 0.5 and not 1.0 because we tried Getting each item twice
   363  	require.Equal(t, 0.5, c.Metrics.Ratio())
   364  
   365  	c = nil
   366  	val, ok = c.Get("0")
   367  	require.False(t, ok)
   368  	require.Nil(t, val)
   369  }
   370  
   371  // retrySet calls SetWithCost until the item is accepted by the cache.
   372  func retrySet(t *testing.T, c *Cache, key string, value int, cost int64) {
   373  	for {
   374  		if set := c.SetWithCost(key, value, cost); !set {
   375  			time.Sleep(wait)
   376  			continue
   377  		}
   378  
   379  		time.Sleep(wait)
   380  		val, ok := c.Get(key)
   381  		require.True(t, ok)
   382  		require.NotNil(t, val)
   383  		require.Equal(t, value, val.(int))
   384  		return
   385  	}
   386  }
   387  
   388  func TestCacheSet(t *testing.T) {
   389  	c, err := NewCache(&Config{
   390  		NumCounters:        100,
   391  		MaxCost:            10,
   392  		IgnoreInternalCost: true,
   393  		BufferItems:        64,
   394  		Metrics:            true,
   395  	})
   396  	require.NoError(t, err)
   397  
   398  	retrySet(t, c, "1", 1, 1)
   399  
   400  	c.SetWithCost("1", 2, 2)
   401  	val, ok := c.store.Get(defaultStringHash("1"))
   402  	require.True(t, ok)
   403  	require.Equal(t, 2, val.(int))
   404  
   405  	c.stop <- struct{}{}
   406  	for i := 0; i < setBufSize; i++ {
   407  		key, conflict := defaultStringHash("1")
   408  		c.setBuf <- &Item{
   409  			flag:     itemUpdate,
   410  			Key:      key,
   411  			Conflict: conflict,
   412  			Value:    1,
   413  			Cost:     1,
   414  		}
   415  	}
   416  	require.False(t, c.SetWithCost("2", 2, 1))
   417  	require.Equal(t, uint64(1), c.Metrics.SetsDropped())
   418  	close(c.setBuf)
   419  	close(c.stop)
   420  
   421  	c = nil
   422  	require.False(t, c.SetWithCost("1", 1, 1))
   423  }
   424  
   425  func TestCacheInternalCost(t *testing.T) {
   426  	c, err := NewCache(&Config{
   427  		NumCounters: 100,
   428  		MaxCost:     10,
   429  		BufferItems: 64,
   430  		Metrics:     true,
   431  	})
   432  	require.NoError(t, err)
   433  
   434  	// Get should return false because the cache's cost is too small to store the item
   435  	// when accounting for the internal cost.
   436  	c.SetWithCost("1", 1, 1)
   437  	time.Sleep(wait)
   438  	_, ok := c.Get("1")
   439  	require.False(t, ok)
   440  }
   441  
   442  func TestCacheDel(t *testing.T) {
   443  	c, err := NewCache(&Config{
   444  		NumCounters: 100,
   445  		MaxCost:     10,
   446  		BufferItems: 64,
   447  	})
   448  	require.NoError(t, err)
   449  
   450  	c.SetWithCost("1", 1, 1)
   451  	c.Delete("1")
   452  	// The deletes and sets are pushed through the setbuf. It might be possible
   453  	// that the delete is not processed before the following get is called. So
   454  	// wait for a millisecond for things to be processed.
   455  	time.Sleep(time.Millisecond)
   456  	val, ok := c.Get("1")
   457  	require.False(t, ok)
   458  	require.Nil(t, val)
   459  
   460  	c = nil
   461  	defer func() {
   462  		require.Nil(t, recover())
   463  	}()
   464  	c.Delete("1")
   465  }
   466  
   467  func TestCacheClear(t *testing.T) {
   468  	c, err := NewCache(&Config{
   469  		NumCounters:        100,
   470  		MaxCost:            10,
   471  		IgnoreInternalCost: true,
   472  		BufferItems:        64,
   473  		Metrics:            true,
   474  	})
   475  	require.NoError(t, err)
   476  
   477  	for i := 0; i < 10; i++ {
   478  		c.SetWithCost(strconv.Itoa(i), i, 1)
   479  	}
   480  	time.Sleep(wait)
   481  	require.Equal(t, uint64(10), c.Metrics.KeysAdded())
   482  
   483  	c.Clear()
   484  	require.Equal(t, uint64(0), c.Metrics.KeysAdded())
   485  
   486  	for i := 0; i < 10; i++ {
   487  		val, ok := c.Get(strconv.Itoa(i))
   488  		require.False(t, ok)
   489  		require.Nil(t, val)
   490  	}
   491  }
   492  
   493  func TestCacheMetrics(t *testing.T) {
   494  	c, err := NewCache(&Config{
   495  		NumCounters:        100,
   496  		MaxCost:            10,
   497  		IgnoreInternalCost: true,
   498  		BufferItems:        64,
   499  		Metrics:            true,
   500  	})
   501  	require.NoError(t, err)
   502  
   503  	for i := 0; i < 10; i++ {
   504  		c.SetWithCost(strconv.Itoa(i), i, 1)
   505  	}
   506  	time.Sleep(wait)
   507  	m := c.Metrics
   508  	require.Equal(t, uint64(10), m.KeysAdded())
   509  }
   510  
   511  func TestMetrics(t *testing.T) {
   512  	newMetrics()
   513  }
   514  
   515  func TestNilMetrics(t *testing.T) {
   516  	var m *Metrics
   517  	for _, f := range []func() uint64{
   518  		m.Hits,
   519  		m.Misses,
   520  		m.KeysAdded,
   521  		m.KeysEvicted,
   522  		m.CostEvicted,
   523  		m.SetsDropped,
   524  		m.SetsRejected,
   525  		m.GetsDropped,
   526  		m.GetsKept,
   527  	} {
   528  		require.Equal(t, uint64(0), f())
   529  	}
   530  }
   531  
   532  func TestMetricsAddGet(t *testing.T) {
   533  	m := newMetrics()
   534  	m.add(hit, 1, 1)
   535  	m.add(hit, 2, 2)
   536  	m.add(hit, 3, 3)
   537  	require.Equal(t, uint64(6), m.Hits())
   538  
   539  	m = nil
   540  	m.add(hit, 1, 1)
   541  	require.Equal(t, uint64(0), m.Hits())
   542  }
   543  
   544  func TestMetricsRatio(t *testing.T) {
   545  	m := newMetrics()
   546  	require.Equal(t, float64(0), m.Ratio())
   547  
   548  	m.add(hit, 1, 1)
   549  	m.add(hit, 2, 2)
   550  	m.add(miss, 1, 1)
   551  	m.add(miss, 2, 2)
   552  	require.Equal(t, 0.5, m.Ratio())
   553  
   554  	m = nil
   555  	require.Equal(t, float64(0), m.Ratio())
   556  }
   557  
   558  func TestMetricsString(t *testing.T) {
   559  	m := newMetrics()
   560  	m.add(hit, 1, 1)
   561  	m.add(miss, 1, 1)
   562  	m.add(keyAdd, 1, 1)
   563  	m.add(keyUpdate, 1, 1)
   564  	m.add(keyEvict, 1, 1)
   565  	m.add(costAdd, 1, 1)
   566  	m.add(costEvict, 1, 1)
   567  	m.add(dropSets, 1, 1)
   568  	m.add(rejectSets, 1, 1)
   569  	m.add(dropGets, 1, 1)
   570  	m.add(keepGets, 1, 1)
   571  	require.Equal(t, uint64(1), m.Hits())
   572  	require.Equal(t, uint64(1), m.Misses())
   573  	require.Equal(t, 0.5, m.Ratio())
   574  	require.Equal(t, uint64(1), m.KeysAdded())
   575  	require.Equal(t, uint64(1), m.KeysUpdated())
   576  	require.Equal(t, uint64(1), m.KeysEvicted())
   577  	require.Equal(t, uint64(1), m.CostAdded())
   578  	require.Equal(t, uint64(1), m.CostEvicted())
   579  	require.Equal(t, uint64(1), m.SetsDropped())
   580  	require.Equal(t, uint64(1), m.SetsRejected())
   581  	require.Equal(t, uint64(1), m.GetsDropped())
   582  	require.Equal(t, uint64(1), m.GetsKept())
   583  
   584  	require.NotEqual(t, 0, len(m.String()))
   585  
   586  	m = nil
   587  	require.Equal(t, 0, len(m.String()))
   588  
   589  	require.Equal(t, "unidentified", stringFor(doNotUse))
   590  }
   591  
   592  func TestCacheMetricsClear(t *testing.T) {
   593  	c, err := NewCache(&Config{
   594  		NumCounters: 100,
   595  		MaxCost:     10,
   596  		BufferItems: 64,
   597  		Metrics:     true,
   598  	})
   599  	require.NoError(t, err)
   600  
   601  	c.SetWithCost("1", 1, 1)
   602  	stop := make(chan struct{})
   603  	go func() {
   604  		for {
   605  			select {
   606  			case <-stop:
   607  				return
   608  			default:
   609  				c.Get("1")
   610  			}
   611  		}
   612  	}()
   613  	time.Sleep(wait)
   614  	c.Clear()
   615  	stop <- struct{}{}
   616  	c.Metrics = nil
   617  	c.Metrics.Clear()
   618  }
   619  
   620  // Regression test for bug https://github.com/dgraph-io/ristretto/issues/167
   621  func TestDropUpdates(t *testing.T) {
   622  	originalSetBugSize := setBufSize
   623  	defer func() { setBufSize = originalSetBugSize }()
   624  
   625  	test := func() {
   626  		// dropppedMap stores the items dropped from the cache.
   627  		droppedMap := make(map[int]struct{})
   628  		lastEvictedSet := int64(-1)
   629  
   630  		var err error
   631  		handler := func(_ interface{}, value interface{}) {
   632  			v := value.(string)
   633  			lastEvictedSet, err = strconv.ParseInt(string(v), 10, 32)
   634  			require.NoError(t, err)
   635  
   636  			_, ok := droppedMap[int(lastEvictedSet)]
   637  			if ok {
   638  				panic(fmt.Sprintf("val = %+v was dropped but it got evicted. Dropped items: %+v\n",
   639  					lastEvictedSet, droppedMap))
   640  			}
   641  		}
   642  
   643  		// This is important. The race condition shows up only when the setBuf
   644  		// is full and that's why we reduce the buf size here. The test will
   645  		// try to fill up the setbuf to it's capacity and then perform an
   646  		// update on a key.
   647  		setBufSize = 10
   648  
   649  		c, err := NewCache(&Config{
   650  			NumCounters: 100,
   651  			MaxCost:     10,
   652  			BufferItems: 64,
   653  			Metrics:     true,
   654  			OnEvict: func(item *Item) {
   655  				if item.Value != nil {
   656  					handler(nil, item.Value)
   657  				}
   658  			},
   659  		})
   660  		require.NoError(t, err)
   661  
   662  		for i := 0; i < 5*setBufSize; i++ {
   663  			v := fmt.Sprintf("%0100d", i)
   664  			// We're updating the same key.
   665  			if !c.SetWithCost("0", v, 1) {
   666  				// The race condition doesn't show up without this sleep.
   667  				time.Sleep(time.Microsecond)
   668  				droppedMap[i] = struct{}{}
   669  			}
   670  		}
   671  		// Wait for all the items to be processed.
   672  		c.Wait()
   673  		// This will cause eviction from the cache.
   674  		require.True(t, c.SetWithCost("1", nil, 10))
   675  		c.Close()
   676  	}
   677  
   678  	// Run the test 100 times since it's not reliable.
   679  	for i := 0; i < 100; i++ {
   680  		test()
   681  	}
   682  }
   683  
   684  func newTestCache() (*Cache, error) {
   685  	return NewCache(&Config{
   686  		NumCounters: 100,
   687  		MaxCost:     10,
   688  		BufferItems: 64,
   689  		Metrics:     true,
   690  	})
   691  }