github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/storage/mem_tracker.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 ) 26 27 type memoryTracker struct { 28 sync.Mutex 29 30 opts MemoryTrackerOptions 31 32 numLoadedBytes int64 33 numPendingLoadedBytes int64 34 35 waitForDecWg *sync.WaitGroup 36 } 37 38 func (m *memoryTracker) IncNumLoadedBytes(x int64) (okToLoad bool) { 39 m.Lock() 40 defer m.Unlock() 41 limit := m.opts.numLoadedBytesLimit 42 if limit == 0 { 43 // Limit of 0 means no limit. 44 return true 45 } 46 // This check is optimistic in the sense that as long as the number of loaded bytes 47 // is currently under the limit then x is accepted, regardless of how far over the 48 // limit it brings the value of numLoadedBytes. 49 // 50 // The reason for this is to avoid scenarios where some process gets permanently 51 // stuck because the amount of data it needs to load at once is larger than the limit 52 // and as a result it's never able to make any progress. 53 // 54 // In practice this implementation should be fine for the vast majority of configurations 55 // and workloads. 56 if m.numLoadedBytes < limit { 57 m.numLoadedBytes += x 58 return true 59 } 60 61 return false 62 } 63 64 func (m *memoryTracker) NumLoadedBytes() int64 { 65 m.Lock() 66 defer m.Unlock() 67 return m.numLoadedBytes 68 } 69 70 func (m *memoryTracker) MarkLoadedAsPending() { 71 m.Lock() 72 m.numPendingLoadedBytes = m.numLoadedBytes 73 m.Unlock() 74 } 75 76 func (m *memoryTracker) DecPendingLoadedBytes() { 77 m.Lock() 78 m.numLoadedBytes -= m.numPendingLoadedBytes 79 m.numPendingLoadedBytes = 0 80 if m.waitForDecWg != nil { 81 m.waitForDecWg.Done() 82 m.waitForDecWg = nil 83 } 84 m.Unlock() 85 } 86 87 func (m *memoryTracker) WaitForDec() { 88 m.Lock() 89 if m.waitForDecWg == nil { 90 m.waitForDecWg = &sync.WaitGroup{} 91 m.waitForDecWg.Add(1) 92 } 93 wg := m.waitForDecWg 94 m.Unlock() 95 wg.Wait() 96 } 97 98 // MemoryTrackerOptions are the options for the MemoryTracker. 99 type MemoryTrackerOptions struct { 100 numLoadedBytesLimit int64 101 } 102 103 // NewMemoryTrackerOptions creates a new MemoryTrackerOptions. 104 func NewMemoryTrackerOptions(numLoadedBytesLimit int64) MemoryTrackerOptions { 105 return MemoryTrackerOptions{ 106 numLoadedBytesLimit: numLoadedBytesLimit, 107 } 108 } 109 110 // NewMemoryTracker creates a new MemoryTracker. 111 func NewMemoryTracker(opts MemoryTrackerOptions) MemoryTracker { 112 return &memoryTracker{ 113 opts: opts, 114 } 115 }