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 }