github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/metrics/matcher/cache/cache_test.go (about)

     1  // Copyright (c) 2017 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package cache
    22  
    23  import (
    24  	"bytes"
    25  	"errors"
    26  	"fmt"
    27  	"math"
    28  	"sync"
    29  	"testing"
    30  	"time"
    31  
    32  	"github.com/m3db/m3/src/metrics/matcher/namespace"
    33  	"github.com/m3db/m3/src/metrics/metric/id"
    34  	"github.com/m3db/m3/src/metrics/rules"
    35  	"github.com/m3db/m3/src/x/clock"
    36  
    37  	"github.com/stretchr/testify/require"
    38  )
    39  
    40  var (
    41  	errTestWaitUntilTimeout = errors.New("test timed out waiting for condition")
    42  	testEmptyMatchResult    = rules.EmptyMatchResult
    43  	testWaitTimeout         = 200 * time.Millisecond
    44  	testValues              = []testValue{
    45  		{namespace: []byte("nsfoo"), id: []byte("foo"), result: testValidResults[0]},
    46  		{namespace: []byte("nsbar"), id: []byte("bar"), result: testValidResults[1]},
    47  	}
    48  )
    49  
    50  func TestCacheMatchNamespaceDoesNotExist(t *testing.T) {
    51  	opts := testCacheOptions()
    52  	c := NewCache(opts)
    53  
    54  	res, err := c.ForwardMatch(namespace.NewTestID("foo", "nonexistentNs"), 0, 0, rules.MatchOptions{})
    55  	require.NoError(t, err)
    56  	require.Equal(t, testEmptyMatchResult, res)
    57  }
    58  
    59  func TestCacheMatchIDCachedValidNoPromotion(t *testing.T) {
    60  	opts := testCacheOptions()
    61  	c := NewCache(opts).(*cache)
    62  	now := time.Now()
    63  	c.nowFn = func() time.Time { return now }
    64  	source := newMockSource()
    65  	populateCache(c, testValues, now.Add(time.Minute), source, populateBoth)
    66  
    67  	// Get the second id and assert we didn't perform a promotion.
    68  	res, err := c.ForwardMatch(testValues[1].ID(), now.UnixNano(), now.UnixNano(), rules.MatchOptions{})
    69  	require.NoError(t, err)
    70  	require.Equal(t, testValues[1].result, res)
    71  	validateCache(t, c, testValues)
    72  }
    73  
    74  func TestCacheMatchIDCachedValidWithPromotion(t *testing.T) {
    75  	opts := testCacheOptions()
    76  	c := NewCache(opts).(*cache)
    77  	now := time.Now()
    78  	c.nowFn = func() time.Time { return now }
    79  	source := newMockSource()
    80  	populateCache(c, testValues, now, source, populateBoth)
    81  
    82  	// Move the time and assert we performed a promotion.
    83  	now = now.Add(time.Minute)
    84  	res, err := c.ForwardMatch(testValues[1].ID(), now.UnixNano(), now.UnixNano(), rules.MatchOptions{})
    85  	require.NoError(t, err)
    86  	require.Equal(t, testValues[1].result, res)
    87  	expected := []testValue{testValues[1], testValues[0]}
    88  	validateCache(t, c, expected)
    89  }
    90  
    91  func TestCacheMatchIDCachedInvalidSourceValidInvalidateAll(t *testing.T) {
    92  	opts := testCacheOptions()
    93  	c := NewCache(opts).(*cache)
    94  	now := time.Now()
    95  	c.nowFn = func() time.Time { return now }
    96  	source := newMockSource()
    97  	input := []testValue{
    98  		{
    99  			namespace: testValues[1].namespace,
   100  			id:        testValues[0].id,
   101  			result:    testValues[0].result,
   102  		},
   103  		{
   104  			namespace: testValues[1].namespace,
   105  			id:        testValues[1].id,
   106  			result:    rules.NewMatchResult(0, now.Add(time.Second).UnixNano(), nil, nil, true),
   107  		},
   108  	}
   109  	populateCache(c, input, now, source, populateBoth)
   110  	entry, ok := c.namespaces.Get(testValues[1].namespace)
   111  	require.True(t, ok)
   112  	require.Equal(t, 2, entry.elems.Len())
   113  
   114  	var (
   115  		ns         = testValues[1].namespace
   116  		id         = testValues[1].id
   117  		newVersion = 3
   118  	)
   119  	result := rules.NewMatchResult(0, math.MaxInt64, testForExistingID, testForNewRollupIDs, true)
   120  	source.setVersion(newVersion)
   121  	source.setResult(id, result)
   122  
   123  	entry, ok = c.namespaces.Get(ns)
   124  	require.True(t, ok)
   125  	require.Equal(t, 2, entry.elems.Len())
   126  	res, err := c.ForwardMatch(testValues[1].ID(), now.UnixNano(), now.Add(time.Minute).UnixNano(), rules.MatchOptions{})
   127  	require.NoError(t, err)
   128  	require.Equal(t, result, res)
   129  
   130  	// Wait for deletion to happen
   131  	conditionFn := func() bool {
   132  		c.list.Lock()
   133  		len := c.list.Len()
   134  		c.list.Unlock()
   135  		return len == 1
   136  	}
   137  	require.NoError(t, testWaitUntilWithTimeout(conditionFn, testWaitTimeout))
   138  
   139  	expected := []testValue{{namespace: ns, id: id, result: result}}
   140  	require.Equal(t, 1, c.namespaces.Len())
   141  	entry, ok = c.namespaces.Get(ns)
   142  	require.True(t, ok)
   143  	require.Equal(t, 1, entry.elems.Len())
   144  	elem, exists := entry.elems.Get(id)
   145  	require.True(t, exists)
   146  	require.True(t, elem == c.list.Front())
   147  	validateCache(t, c, expected)
   148  }
   149  
   150  func TestCacheMatchIDCachedInvalidSourceValidInvalidateAllNoEviction(t *testing.T) {
   151  	opts := testCacheOptions()
   152  	c := NewCache(opts).(*cache)
   153  	now := time.Now()
   154  	c.nowFn = func() time.Time { return now }
   155  	source := newMockSource()
   156  	input := []testValue{
   157  		{namespace: testValues[1].namespace, id: testValues[0].id, result: testValues[0].result},
   158  		{namespace: testValues[1].namespace, id: testValues[1].id, result: testExpiredResults[1]},
   159  	}
   160  	populateCache(c, input, now, source, populateBoth)
   161  	entry, ok := c.namespaces.Get(testValues[1].namespace)
   162  	require.True(t, ok)
   163  	require.Equal(t, 2, entry.elems.Len())
   164  
   165  	var (
   166  		ns         = testValues[1].namespace
   167  		id         = testValues[1].id
   168  		newVersion = 3
   169  	)
   170  	result := rules.NewMatchResult(0, math.MaxInt64, testForExistingID, testForNewRollupIDs, true)
   171  	source.setVersion(newVersion)
   172  	source.setResult(id, result)
   173  
   174  	entry, ok = c.namespaces.Get(ns)
   175  	require.True(t, ok)
   176  	require.Equal(t, 2, entry.elems.Len())
   177  	res, err := c.ForwardMatch(testValues[1].ID(), now.UnixNano(), now.UnixNano(), rules.MatchOptions{})
   178  	require.NoError(t, err)
   179  	require.Equal(t, result, res)
   180  
   181  	// Wait for deletion to happen
   182  	conditionFn := func() bool {
   183  		c.list.Lock()
   184  		len := c.list.Len()
   185  		c.list.Unlock()
   186  		return len == 1
   187  	}
   188  	require.NoError(t, testWaitUntilWithTimeout(conditionFn, testWaitTimeout))
   189  
   190  	expected := []testValue{{namespace: ns, id: id, result: result}}
   191  	require.Equal(t, 1, c.namespaces.Len())
   192  	entry, ok = c.namespaces.Get(ns)
   193  	require.True(t, ok)
   194  	require.Equal(t, 1, entry.elems.Len())
   195  	elem, exists := entry.elems.Get(id)
   196  	require.True(t, exists)
   197  	require.True(t, elem == c.list.Front())
   198  	validateCache(t, c, expected)
   199  }
   200  
   201  func TestCacheMatchIDCachedInvalidSourceValidInvalidateOneNoEviction(t *testing.T) {
   202  	opts := testCacheOptions().SetInvalidationMode(InvalidateOne)
   203  	c := NewCache(opts).(*cache)
   204  	now := time.Now()
   205  	c.nowFn = func() time.Time { return now }
   206  	source := newMockSource()
   207  	input := []testValue{
   208  		{namespace: testValues[1].namespace, id: testValues[0].id, result: testValues[0].result},
   209  		{namespace: testValues[1].namespace, id: testValues[1].id, result: testExpiredResults[1]},
   210  	}
   211  	populateCache(c, input, now, source, populateBoth)
   212  
   213  	var (
   214  		ns         = testValues[1].namespace
   215  		id         = testValues[1].id
   216  		newVersion = 3
   217  	)
   218  	result := rules.NewMatchResult(0, math.MaxInt64, testForExistingID, testForNewRollupIDs, true)
   219  	source.setVersion(newVersion)
   220  	source.setResult(id, result)
   221  
   222  	entry, ok := c.namespaces.Get(ns)
   223  	require.True(t, ok)
   224  	require.Equal(t, 2, entry.elems.Len())
   225  	res, err := c.ForwardMatch(testValues[1].ID(), now.UnixNano(), now.UnixNano(), rules.MatchOptions{})
   226  	require.NoError(t, err)
   227  	require.Equal(t, result, res)
   228  
   229  	// Wait for deletion to happen.
   230  	conditionFn := func() bool {
   231  		c.list.Lock()
   232  		len := c.list.Len()
   233  		c.list.Unlock()
   234  		return len == 2
   235  	}
   236  	require.NoError(t, testWaitUntilWithTimeout(conditionFn, testWaitTimeout))
   237  
   238  	expected := []testValue{
   239  		{namespace: ns, id: id, result: result},
   240  		{namespace: ns, id: testValues[0].id, result: testValues[0].result},
   241  	}
   242  	entry, ok = c.namespaces.Get(ns)
   243  	require.True(t, ok)
   244  	require.Equal(t, 1, c.namespaces.Len())
   245  	require.Equal(t, 2, entry.elems.Len())
   246  	elem, exists := entry.elems.Get(id)
   247  	require.True(t, exists)
   248  	require.True(t, elem == c.list.Front())
   249  	validateCache(t, c, expected)
   250  }
   251  
   252  func TestCacheMatchIDCachedInvalidSourceValidWithEviction(t *testing.T) {
   253  	opts := testCacheOptions().SetInvalidationMode(InvalidateOne)
   254  	c := NewCache(opts).(*cache)
   255  	now := time.Now()
   256  	c.nowFn = func() time.Time { return now }
   257  	source := newMockSource()
   258  	input := []testValue{
   259  		{namespace: []byte("ns1"), id: []byte("foo"), result: testExpiredResults[0]},
   260  		{namespace: []byte("ns1"), id: []byte("bar"), result: testExpiredResults[0]},
   261  		{namespace: []byte("ns2"), id: []byte("baz"), result: testExpiredResults[1]},
   262  		{namespace: []byte("ns2"), id: []byte("cat"), result: testExpiredResults[1]},
   263  	}
   264  	populateCache(c, input, now, source, populateBoth)
   265  
   266  	newVersion := 3
   267  	newResult := rules.NewMatchResult(
   268  		0,
   269  		math.MaxInt64,
   270  		testForExistingID,
   271  		testForNewRollupIDs,
   272  		true,
   273  	)
   274  	source.setVersion(newVersion)
   275  	for _, id := range []string{"foo", "bar", "baz", "cat", "lol"} {
   276  		source.setResult([]byte(id), newResult)
   277  	}
   278  
   279  	// Retrieve a few ids and assert we don't evict due to eviction batching.
   280  	for _, value := range []testValue{
   281  		{namespace: []byte("ns1"), id: []byte("foo")},
   282  		{namespace: []byte("ns1"), id: []byte("bar")},
   283  		{namespace: []byte("ns2"), id: []byte("baz")},
   284  		{namespace: []byte("ns2"), id: []byte("cat")},
   285  	} {
   286  		res, err := c.ForwardMatch(value.ID(), now.UnixNano(), now.UnixNano(), rules.MatchOptions{})
   287  		require.NoError(t, err)
   288  		require.Equal(t, newResult, res)
   289  	}
   290  	conditionFn := func() bool {
   291  		c.list.Lock()
   292  		len := c.list.Len()
   293  		c.list.Unlock()
   294  		return len == c.capacity
   295  	}
   296  	require.Equal(t, errTestWaitUntilTimeout, testWaitUntilWithTimeout(conditionFn, testWaitTimeout))
   297  	expected := []testValue{
   298  		{namespace: []byte("ns2"), id: []byte("cat"), result: newResult},
   299  		{namespace: []byte("ns2"), id: []byte("baz"), result: newResult},
   300  		{namespace: []byte("ns1"), id: []byte("bar"), result: newResult},
   301  		{namespace: []byte("ns1"), id: []byte("foo"), result: newResult},
   302  	}
   303  	validateCache(t, c, expected)
   304  
   305  	// Retrieve one more id and assert we perform async eviction.
   306  	c.invalidationMode = InvalidateAll
   307  	res, err := c.ForwardMatch(namespace.NewTestID("lol", "ns1"),
   308  		now.UnixNano(), now.UnixNano(), rules.MatchOptions{})
   309  	require.NoError(t, err)
   310  	require.Equal(t, newResult, res)
   311  	require.NoError(t, testWaitUntilWithTimeout(conditionFn, testWaitTimeout))
   312  	expected = []testValue{
   313  		{namespace: []byte("ns1"), id: []byte("lol"), result: newResult},
   314  		{namespace: []byte("ns2"), id: []byte("cat"), result: newResult},
   315  	}
   316  	validateCache(t, c, expected)
   317  }
   318  
   319  func TestCacheMatchIDNotCachedAndDoesNotExistInSource(t *testing.T) {
   320  	opts := testCacheOptions()
   321  	c := NewCache(opts).(*cache)
   322  	now := time.Now()
   323  	c.nowFn = func() time.Time { return now }
   324  	source := newMockSource()
   325  	populateCache(c, testValues, now.Add(time.Minute), source, populateBoth)
   326  
   327  	res, err := c.ForwardMatch(namespace.NewTestID("nonExistent", "nsfoo"),
   328  		now.UnixNano(), now.UnixNano(), rules.MatchOptions{})
   329  	require.NoError(t, err)
   330  	require.Equal(t, testEmptyMatchResult, res)
   331  }
   332  
   333  func TestCacheMatchIDNotCachedSourceValidNoEviction(t *testing.T) {
   334  	opts := testCacheOptions()
   335  	c := NewCache(opts).(*cache)
   336  	now := time.Now()
   337  	c.nowFn = func() time.Time { return now }
   338  	source := newMockSource()
   339  	populateCache(c, []testValue{testValues[1]}, now, source, populateSource)
   340  
   341  	var (
   342  		ns     = testValues[1].namespace
   343  		id     = testValues[1].id
   344  		result = testValues[1].result
   345  	)
   346  	entry, ok := c.namespaces.Get(ns)
   347  	require.True(t, ok)
   348  	require.Equal(t, 0, entry.elems.Len())
   349  	res, err := c.ForwardMatch(testValues[1].ID(), now.UnixNano(), now.UnixNano(), rules.MatchOptions{})
   350  	require.NoError(t, err)
   351  	require.Equal(t, result, res)
   352  
   353  	expected := []testValue{testValues[1]}
   354  	elem, exists := entry.elems.Get(id)
   355  	require.True(t, exists)
   356  	require.True(t, elem == c.list.Front())
   357  	validateCache(t, c, expected)
   358  }
   359  
   360  func TestCacheMatchParallel(t *testing.T) {
   361  	opts := testCacheOptions()
   362  	c := NewCache(opts).(*cache)
   363  	now := time.Now()
   364  	c.nowFn = func() time.Time { return now }
   365  	source := newMockSource()
   366  	input := []testValue{
   367  		{namespace: []byte("ns1"), id: []byte("foo"), result: testExpiredResults[0]},
   368  		{namespace: []byte("ns2"), id: []byte("baz"), result: testExpiredResults[1]},
   369  	}
   370  	populateCache(c, input, now, source, populateBoth)
   371  
   372  	newVersion := 3
   373  	nowNanos := time.Now().UnixNano()
   374  	newResult := rules.NewMatchResult(0, nowNanos, testForExistingID, testForNewRollupIDs, true)
   375  	source.setVersion(newVersion)
   376  	for _, id := range []string{"foo", "baz"} {
   377  		source.setResult([]byte(id), newResult)
   378  	}
   379  
   380  	var wg sync.WaitGroup
   381  	for i := 0; i < 1000; i++ {
   382  		v := input[i%2]
   383  		wg.Add(1)
   384  		go func() {
   385  			defer wg.Done()
   386  			res, err := c.ForwardMatch(v.ID(), now.UnixNano(), now.UnixNano(), rules.MatchOptions{})
   387  			require.NoError(t, err)
   388  			require.Equal(t, newResult, res)
   389  		}()
   390  	}
   391  	wg.Wait()
   392  
   393  	if bytes.Equal(c.list.Front().id, input[0].id) {
   394  		validateCache(t, c, []testValue{
   395  			{namespace: []byte("ns1"), id: []byte("foo"), result: newResult},
   396  			{namespace: []byte("ns2"), id: []byte("baz"), result: newResult},
   397  		})
   398  	} else {
   399  		validateCache(t, c, []testValue{
   400  			{namespace: []byte("ns2"), id: []byte("baz"), result: newResult},
   401  			{namespace: []byte("ns1"), id: []byte("foo"), result: newResult},
   402  		})
   403  	}
   404  }
   405  
   406  func TestCacheRegisterNamespaceDoesNotExist(t *testing.T) {
   407  	opts := testCacheOptions()
   408  	c := NewCache(opts).(*cache)
   409  	now := time.Now()
   410  	c.nowFn = func() time.Time { return now }
   411  	require.Equal(t, 0, c.namespaces.Len())
   412  
   413  	var (
   414  		ns     = []byte("ns")
   415  		source = newMockSource()
   416  	)
   417  	c.Register(ns, source)
   418  	require.Equal(t, 1, c.namespaces.Len())
   419  	entry, ok := c.namespaces.Get(ns)
   420  	require.True(t, ok)
   421  	require.Equal(t, 0, entry.elems.Len())
   422  	require.Equal(t, source, entry.source)
   423  }
   424  
   425  func TestCacheRegisterNamespaceExists(t *testing.T) {
   426  	opts := testCacheOptions()
   427  	c := NewCache(opts).(*cache)
   428  	now := time.Now()
   429  	c.nowFn = func() time.Time { return now }
   430  	populateCache(c, []testValue{testValues[0]}, now, nil, populateBoth)
   431  
   432  	ns := testValues[0].namespace
   433  	require.Equal(t, 1, c.namespaces.Len())
   434  	entry, ok := c.namespaces.Get(ns)
   435  	require.True(t, ok)
   436  	require.Equal(t, 1, entry.elems.Len())
   437  	require.Nil(t, entry.source)
   438  
   439  	source := newMockSource()
   440  	c.Register(ns, source)
   441  
   442  	// Wait till the outdated cached data are deleted.
   443  	conditionFn := func() bool {
   444  		c.list.Lock()
   445  		len := c.list.Len()
   446  		c.list.Unlock()
   447  		return len == 0
   448  	}
   449  	require.NoError(t, testWaitUntilWithTimeout(conditionFn, testWaitTimeout))
   450  
   451  	require.Equal(t, 1, c.namespaces.Len())
   452  	entry, ok = c.namespaces.Get(ns)
   453  	require.True(t, ok)
   454  	require.Equal(t, 0, entry.elems.Len())
   455  	require.Equal(t, source, entry.source)
   456  }
   457  
   458  func TestCacheRefreshNamespaceDoesNotExist(t *testing.T) {
   459  	opts := testCacheOptions()
   460  	c := NewCache(opts).(*cache)
   461  	require.Equal(t, 0, c.namespaces.Len())
   462  
   463  	var (
   464  		ns     = []byte("ns")
   465  		source = newMockSource()
   466  	)
   467  	c.Refresh(ns, source)
   468  	require.Equal(t, 0, c.namespaces.Len())
   469  }
   470  
   471  func TestCacheRefreshStaleSource(t *testing.T) {
   472  	opts := testCacheOptions()
   473  	c := NewCache(opts).(*cache)
   474  	require.Equal(t, 0, c.namespaces.Len())
   475  
   476  	var (
   477  		ns      = []byte("ns")
   478  		source1 = newMockSource()
   479  		source2 = newMockSource()
   480  	)
   481  	c.Register(ns, source1)
   482  	require.Equal(t, 1, c.namespaces.Len())
   483  
   484  	c.Refresh(ns, source2)
   485  	require.Equal(t, 1, c.namespaces.Len())
   486  	entry, ok := c.namespaces.Get(ns)
   487  	require.True(t, ok)
   488  	require.Equal(t, source1, entry.source)
   489  }
   490  
   491  func TestCacheRefreshSuccess(t *testing.T) {
   492  	opts := testCacheOptions()
   493  	c := NewCache(opts).(*cache)
   494  	now := time.Now()
   495  	c.nowFn = func() time.Time { return now }
   496  
   497  	var (
   498  		ns  = testValues[0].namespace
   499  		src = newMockSource()
   500  	)
   501  	populateCache(c, []testValue{testValues[0]}, now, src, populateBoth)
   502  	require.Equal(t, 1, c.namespaces.Len())
   503  	entry, ok := c.namespaces.Get(ns)
   504  	require.True(t, ok)
   505  	require.Equal(t, 1, entry.elems.Len())
   506  	require.Equal(t, src, entry.source)
   507  
   508  	c.Refresh(ns, src)
   509  	entry, ok = c.namespaces.Get(ns)
   510  	require.True(t, ok)
   511  	require.Equal(t, 1, c.namespaces.Len())
   512  	require.Equal(t, 0, entry.elems.Len())
   513  	require.Equal(t, src, entry.source)
   514  }
   515  
   516  func TestCacheUnregisterNamespaceDoesNotExist(t *testing.T) {
   517  	opts := testCacheOptions()
   518  	c := NewCache(opts).(*cache)
   519  	now := time.Now()
   520  	c.nowFn = func() time.Time { return now }
   521  	populateCache(c, testValues, now, nil, populateBoth)
   522  
   523  	// Delete a namespace that doesn't exist.
   524  	c.Unregister([]byte("nonexistent"))
   525  
   526  	// Wait a little in case anything unexpected would happen.
   527  	time.Sleep(100 * time.Millisecond)
   528  
   529  	validateCache(t, c, testValues)
   530  }
   531  
   532  func TestCacheUnregisterNamespaceExists(t *testing.T) {
   533  	opts := testCacheOptions()
   534  	c := NewCache(opts).(*cache)
   535  	now := time.Now()
   536  	c.nowFn = func() time.Time { return now }
   537  	populateCache(c, testValues, now, nil, populateBoth)
   538  
   539  	// Delete a namespace.
   540  	for _, value := range testValues {
   541  		c.Unregister(value.namespace)
   542  	}
   543  
   544  	// Wait till the namespace is deleted.
   545  	conditionFn := func() bool {
   546  		c.list.Lock()
   547  		len := c.list.Len()
   548  		c.list.Unlock()
   549  		return len == 0
   550  	}
   551  	require.NoError(t, testWaitUntilWithTimeout(conditionFn, testWaitTimeout))
   552  
   553  	// Assert the value has been deleted.
   554  	validateCache(t, c, nil)
   555  }
   556  
   557  func TestCacheDeleteBatching(t *testing.T) {
   558  	opts := testCacheOptions().SetDeletionBatchSize(10)
   559  	c := NewCache(opts).(*cache)
   560  	now := time.Now()
   561  	c.nowFn = func() time.Time { return now }
   562  	var intervals []time.Duration
   563  	c.sleepFn = func(d time.Duration) {
   564  		intervals = append(intervals, d)
   565  	}
   566  
   567  	var elemMaps []*elemMap
   568  	for _, value := range testValues {
   569  		m := newElemMap(elemMapOptions{})
   570  		for i := 0; i < 37; i++ {
   571  			elem := &element{
   572  				namespace:   value.namespace,
   573  				id:          []byte(fmt.Sprintf("%s%d", value.id, i)),
   574  				result:      value.result,
   575  				expiryNanos: now.UnixNano(),
   576  			}
   577  			m.Set(elem.id, elem)
   578  			c.list.PushBack(elem)
   579  		}
   580  		elemMaps = append(elemMaps, m)
   581  	}
   582  
   583  	c.Lock()
   584  	c.toDelete = elemMaps
   585  	c.Unlock()
   586  
   587  	// Send the deletion signal.
   588  	c.deleteCh <- struct{}{}
   589  
   590  	// Wait till the namespace is deleted.
   591  	conditionFn := func() bool {
   592  		c.list.Lock()
   593  		len := c.list.Len()
   594  		c.list.Unlock()
   595  		return len == 0
   596  	}
   597  	require.NoError(t, testWaitUntilWithTimeout(conditionFn, testWaitTimeout))
   598  
   599  	// Assert the value has been deleted.
   600  	validateCache(t, c, nil)
   601  
   602  	// Assert we have slept 7 times.
   603  	require.Equal(t, 7, len(intervals))
   604  	for i := 0; i < 7; i++ {
   605  		require.Equal(t, deletionThrottleInterval, intervals[i])
   606  	}
   607  }
   608  
   609  func TestCacheClose(t *testing.T) {
   610  	opts := testCacheOptions()
   611  	c := NewCache(opts).(*cache)
   612  
   613  	// Make sure we can close multiple times.
   614  	require.NoError(t, c.Close())
   615  
   616  	// Make sure the workers have exited.
   617  	c.evictCh <- struct{}{}
   618  	c.deleteCh <- struct{}{}
   619  
   620  	// Sleep a little in case those signals can be consumed.
   621  	time.Sleep(100 * time.Millisecond)
   622  
   623  	// Assert no goroutines are consuming the signals.
   624  	require.Equal(t, 1, len(c.evictCh))
   625  	require.Equal(t, 1, len(c.deleteCh))
   626  
   627  	// Assert closing the cache again will return an error.
   628  	require.Equal(t, errCacheClosed, c.Close())
   629  }
   630  
   631  type populationMode int
   632  
   633  const (
   634  	populateMap    populationMode = 1 << 0
   635  	populateSource populationMode = 1 << 1
   636  	populateBoth   populationMode = populateMap | populateSource
   637  )
   638  
   639  type mockSource struct {
   640  	sync.Mutex
   641  
   642  	idMap       map[string]rules.MatchResult
   643  	currVersion int
   644  }
   645  
   646  func newMockSource() *mockSource {
   647  	return &mockSource{idMap: make(map[string]rules.MatchResult)}
   648  }
   649  
   650  func (s *mockSource) IsValid(version int) bool {
   651  	s.Lock()
   652  	currVersion := s.currVersion
   653  	s.Unlock()
   654  	return version >= currVersion
   655  }
   656  
   657  func (s *mockSource) ForwardMatch(id id.ID, _, _ int64, _ rules.MatchOptions) (rules.MatchResult, error) {
   658  	s.Lock()
   659  	defer s.Unlock()
   660  	if res, exists := s.idMap[string(id.Bytes())]; exists {
   661  		return res, nil
   662  	}
   663  	return rules.EmptyMatchResult, nil
   664  }
   665  
   666  // nolint: unparam
   667  func (s *mockSource) setVersion(version int) {
   668  	s.Lock()
   669  	s.currVersion = version
   670  	s.Unlock()
   671  }
   672  
   673  func (s *mockSource) setResult(id []byte, res rules.MatchResult) {
   674  	s.Lock()
   675  	s.idMap[string(id)] = res
   676  	s.Unlock()
   677  }
   678  
   679  type conditionFn func() bool
   680  
   681  func testWaitUntilWithTimeout(fn conditionFn, dur time.Duration) error {
   682  	start := time.Now()
   683  	for !fn() {
   684  		time.Sleep(100 * time.Millisecond)
   685  		end := time.Now()
   686  		if end.Sub(start) >= dur {
   687  			return errTestWaitUntilTimeout
   688  		}
   689  	}
   690  	return nil
   691  }
   692  
   693  func testCacheOptions() Options {
   694  	return NewOptions().
   695  		SetClockOptions(clock.NewOptions()).
   696  		SetCapacity(2).
   697  		SetFreshDuration(5 * time.Second).
   698  		SetStutterDuration(1 * time.Second).
   699  		SetEvictionBatchSize(2).
   700  		SetDeletionBatchSize(2).
   701  		SetInvalidationMode(InvalidateAll)
   702  }
   703  
   704  func populateCache(
   705  	c *cache,
   706  	values []testValue,
   707  	expiry time.Time,
   708  	source *mockSource,
   709  	mode populationMode,
   710  ) {
   711  	var resultSource rules.Matcher
   712  	if source != nil {
   713  		resultSource = source
   714  	}
   715  	for _, value := range values {
   716  		results, exists := c.namespaces.Get(value.namespace)
   717  		if !exists {
   718  			results = newResults(resultSource)
   719  			c.namespaces.Set(value.namespace, results)
   720  		}
   721  		if (mode & populateMap) > 0 {
   722  			elem := &element{
   723  				namespace:   value.namespace,
   724  				id:          value.id,
   725  				result:      value.result,
   726  				expiryNanos: expiry.UnixNano(),
   727  			}
   728  			results.elems.Set(elem.id, elem)
   729  			c.list.PushBack(elem)
   730  		}
   731  		if (mode&populateSource) > 0 && source != nil {
   732  			source.idMap[string(value.id)] = value.result
   733  		}
   734  	}
   735  }
   736  
   737  func validateCache(t *testing.T, c *cache, expected []testValue) {
   738  	c.list.Lock()
   739  	defer c.list.Unlock()
   740  
   741  	validateList(t, &c.list.list, expected)
   742  	validateNamespaces(t, c.namespaces, &c.list.list, expected)
   743  }
   744  
   745  func validateNamespaces(
   746  	t *testing.T,
   747  	namespaces *namespaceResultsMap,
   748  	l *list,
   749  	expected []testValue,
   750  ) {
   751  	expectedNamespaces := make(map[string][]testValue)
   752  	for _, v := range expected {
   753  		expectedNamespaces[string(v.namespace)] = append(expectedNamespaces[string(v.namespace)], v)
   754  	}
   755  	require.Equal(t, len(expectedNamespaces), namespaces.Len())
   756  	for _, entry := range namespaces.Iter() {
   757  		namespace, results := entry.Key(), entry.Value()
   758  		expectedResults, exists := expectedNamespaces[string(namespace)]
   759  		require.True(t, exists)
   760  		validateResults(t, namespace, results.elems, l, expectedResults)
   761  	}
   762  }
   763  
   764  func validateResults(t *testing.T, namespace []byte, elems *elemMap, l *list, expected []testValue) {
   765  	require.Equal(t, len(expected), elems.Len(),
   766  		fmt.Sprintf("mismatch for namespace: %v", string(namespace)))
   767  	elemMap := make(map[*element]struct{})
   768  	for _, v := range expected {
   769  		e, exists := elems.Get(v.id)
   770  		require.True(t, exists)
   771  		elemMap[e] = struct{}{}
   772  
   773  		// Assert the element is in the list.
   774  		found := false
   775  		for le := l.Front(); le != nil; le = le.next {
   776  			if le == e {
   777  				found = true
   778  				break
   779  			}
   780  		}
   781  		require.True(t, found)
   782  	}
   783  
   784  	// Assert all the elements are unique.
   785  	require.Equal(t, len(expected), len(elemMap))
   786  }