github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/storage/mem_tracker_test.go (about)

     1  // Copyright (c) 2019 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 storage
    22  
    23  import (
    24  	"sync"
    25  	"testing"
    26  
    27  	"github.com/stretchr/testify/require"
    28  )
    29  
    30  func TestMemoryTrackerLoadLimitEnforcedIfSet(t *testing.T) {
    31  	limit := int64(100)
    32  	memTracker := NewMemoryTracker(NewMemoryTrackerOptions(limit))
    33  	// First one will always be allowed, see comment in IncNumLoadedBytes()
    34  	// for explanation.
    35  	require.True(t, memTracker.IncNumLoadedBytes(limit))
    36  	require.False(t, memTracker.IncNumLoadedBytes(1))
    37  }
    38  
    39  func TestMemoryTrackerLoadLimitNotEnforcedIfNotSet(t *testing.T) {
    40  	memTracker := NewMemoryTracker(NewMemoryTrackerOptions(0))
    41  	require.True(t, memTracker.IncNumLoadedBytes(100))
    42  }
    43  
    44  // TestMemoryTrackerDoubleDec ensures that calling Dec twice in a row (which
    45  // should not happen anyways with a well behaved caller) does not cause the
    46  // number of loaded bytes to decrement twice.
    47  func TestMemoryTrackerDoubleDec(t *testing.T) {
    48  	limit := int64(100)
    49  	memTracker := NewMemoryTracker(NewMemoryTrackerOptions(limit))
    50  	require.True(t, memTracker.IncNumLoadedBytes(50))
    51  	memTracker.MarkLoadedAsPending()
    52  	memTracker.DecPendingLoadedBytes()
    53  	memTracker.DecPendingLoadedBytes()
    54  	require.Equal(t, int64(0), memTracker.NumLoadedBytes())
    55  }
    56  
    57  func TestMemoryTrackerIncMarkAndDec(t *testing.T) {
    58  	var (
    59  		limit         = int64(100)
    60  		oneTenthLimit = limit / 10
    61  		memTracker    = NewMemoryTracker(NewMemoryTrackerOptions(limit))
    62  	)
    63  	require.True(t, oneTenthLimit > 1)
    64  
    65  	// Set the maximum.
    66  	require.True(t, memTracker.IncNumLoadedBytes(limit))
    67  	require.Equal(t, limit, memTracker.NumLoadedBytes())
    68  	// Ensure no more can be loaded.
    69  	require.False(t, memTracker.IncNumLoadedBytes(1))
    70  	memTracker.MarkLoadedAsPending()
    71  	// Ensure num loaded wasn't changed by mark.
    72  	require.Equal(t, limit, memTracker.NumLoadedBytes())
    73  	// Ensure no more still can't be loaded even after marking.
    74  	require.False(t, memTracker.IncNumLoadedBytes(1))
    75  	memTracker.DecPendingLoadedBytes()
    76  	// Ensure num loaded is affected by combination of mark + dec.
    77  	require.Equal(t, int64(0), memTracker.NumLoadedBytes())
    78  	// Ensure limit is reset after a combination mark + dec.
    79  	require.True(t, memTracker.IncNumLoadedBytes(limit))
    80  
    81  	// Clear.
    82  	memTracker.MarkLoadedAsPending()
    83  	memTracker.DecPendingLoadedBytes()
    84  	require.Equal(t, int64(0), memTracker.NumLoadedBytes())
    85  
    86  	// Ensure interactions between concurrent loads and marks/decs behave as expected.
    87  	require.True(t, memTracker.IncNumLoadedBytes(oneTenthLimit))
    88  	require.Equal(t, oneTenthLimit, memTracker.NumLoadedBytes())
    89  	memTracker.MarkLoadedAsPending()
    90  	require.True(t, memTracker.IncNumLoadedBytes(oneTenthLimit))
    91  	require.Equal(t, 2*oneTenthLimit, memTracker.NumLoadedBytes())
    92  	memTracker.DecPendingLoadedBytes()
    93  	// There should still be 1/10th pending since the second load was called after the
    94  	// last call to mark before the call to dec.
    95  	require.Equal(t, oneTenthLimit, memTracker.NumLoadedBytes())
    96  
    97  	// Clear.
    98  	memTracker.MarkLoadedAsPending()
    99  	memTracker.DecPendingLoadedBytes()
   100  	require.Equal(t, int64(0), memTracker.NumLoadedBytes())
   101  
   102  	// Ensure calling mark multiple times before a single dec behaves
   103  	// as expected.
   104  	require.True(t, memTracker.IncNumLoadedBytes(oneTenthLimit))
   105  	require.Equal(t, oneTenthLimit, memTracker.NumLoadedBytes())
   106  	memTracker.MarkLoadedAsPending()
   107  	// Imagine an error happened here outside the context of the memtracker
   108  	// so instead of calling dec the process tries again by calling mark once
   109  	// more and then dec'ing after that. Also, in the mean time some more data
   110  	// has been loaded.
   111  	require.True(t, memTracker.IncNumLoadedBytes(oneTenthLimit))
   112  	require.Equal(t, 2*oneTenthLimit, memTracker.NumLoadedBytes())
   113  	memTracker.MarkLoadedAsPending()
   114  	memTracker.DecPendingLoadedBytes()
   115  	require.Equal(t, int64(0), memTracker.NumLoadedBytes())
   116  }
   117  
   118  // TestMemTrackerWaitForDec spins up several goroutines that call MarkLoadedAsPending,
   119  // DecPendingLoadedBytes, and WaitForDec in a loop to ensure that there are no deadlocks
   120  // or race conditions.
   121  func TestMemTrackerWaitForDec(t *testing.T) {
   122  	var (
   123  		numIterations = 1000
   124  		memTracker    = NewMemoryTracker(NewMemoryTrackerOptions(100))
   125  		doneCh        = make(chan struct{})
   126  		wg            sync.WaitGroup
   127  	)
   128  
   129  	// Start a goroutine to call MarkLoadedAsPending() in a loop.
   130  	wg.Add(1)
   131  	go func() {
   132  		defer wg.Done()
   133  		for i := 0; i < numIterations; i++ {
   134  			memTracker.MarkLoadedAsPending()
   135  		}
   136  	}()
   137  
   138  	// Start a goroutine to call WaitForDec() in a loop.
   139  	wg.Add(1)
   140  	go func() {
   141  		defer wg.Done()
   142  		for i := 0; i < numIterations; i++ {
   143  			memTracker.WaitForDec()
   144  		}
   145  	}()
   146  
   147  	// Start a goroutine to call DecPendingLoadedBytes() in a loop. Note that
   148  	// unlike the other two goroutines this one loops infinitely until the test
   149  	// is over. This is to prevent calls to WaitForDec() from getting stuck forever
   150  	// because a call to WaitForDec() was made after the goroutine that calls
   151  	// DecPendingLoadedBytes() had already shut down.
   152  	go func() {
   153  		for {
   154  			select {
   155  			case <-doneCh:
   156  				return
   157  			default:
   158  				memTracker.DecPendingLoadedBytes()
   159  			}
   160  		}
   161  	}()
   162  
   163  	// Ensure all the goroutines exit cleanly (ensuring no deadlocks.)
   164  	wg.Wait()
   165  
   166  	// Stop the background goroutine calling DecPendingLoadedBytes().
   167  	close(doneCh)
   168  }