github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/utils/reftracker/reftracker.go (about) 1 /* 2 * Copyright (C) 2021 The "MysteriumNetwork/node" Authors. 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 package reftracker 19 20 import ( 21 "errors" 22 "sync" 23 "time" 24 ) 25 26 // DefaultPatrolPeriod is an interval between collect loops 27 const DefaultPatrolPeriod = 1 * time.Second 28 29 var ( 30 singletonOnce sync.Once 31 singletonInst *RefTracker 32 ) 33 34 var ( 35 // ErrNotFound is returned when incremented or decremented element is not 36 // found among tracked elements 37 ErrNotFound = errors.New("element not found") 38 ) 39 40 // Singleton instantiates globally available reference tracker 41 func Singleton() *RefTracker { 42 singletonOnce.Do(func() { 43 singletonInst = NewRefTracker(DefaultPatrolPeriod) 44 }) 45 46 return singletonInst 47 } 48 49 // RefTracker is a collection of elements referenced by string keys. 50 // Once element reference count hits zero it will be removed after TTL 51 // unless new references will not appear. 52 type RefTracker struct { 53 elemMux sync.Mutex 54 elements map[string]*trackedElement 55 stop chan struct{} 56 stopOnce sync.Once 57 } 58 59 type trackedElement struct { 60 lastReleased time.Time 61 ttl time.Duration 62 releaseCallback func() 63 refCount int 64 } 65 66 // NewRefTracker starts RefTracker with 67 func NewRefTracker(patrolPeriod time.Duration) *RefTracker { 68 rt := &RefTracker{ 69 elements: make(map[string]*trackedElement), 70 stop: make(chan struct{}), 71 } 72 go rt.patrolLoop(patrolPeriod) 73 return rt 74 } 75 76 func (rt *RefTracker) patrolLoop(patrolPeriod time.Duration) { 77 for { 78 select { 79 case <-rt.stop: 80 return 81 case <-time.After(patrolPeriod): 82 now := time.Now() 83 var toDelete []string 84 85 rt.elemMux.Lock() 86 for key, elem := range rt.elements { 87 if elem.refCount == 0 && now.Sub(elem.lastReleased) > elem.ttl { 88 toDelete = append(toDelete, key) 89 go elem.releaseCallback() 90 } 91 } 92 93 for _, key := range toDelete { 94 delete(rt.elements, key) 95 } 96 rt.elemMux.Unlock() 97 } 98 } 99 } 100 101 // Put introduces tracked element along with its TTL and release callback 102 func (rt *RefTracker) Put(key string, ttl time.Duration, releaseCallback func()) { 103 elem := &trackedElement{ 104 lastReleased: time.Now(), 105 ttl: ttl, 106 releaseCallback: releaseCallback, 107 refCount: 0, 108 } 109 110 rt.elemMux.Lock() 111 defer rt.elemMux.Unlock() 112 113 _, ok := rt.elements[key] 114 if !ok { 115 rt.elements[key] = elem 116 } 117 } 118 119 // Incr increments reference count of an element 120 func (rt *RefTracker) Incr(key string) error { 121 rt.elemMux.Lock() 122 defer rt.elemMux.Unlock() 123 124 elem, ok := rt.elements[key] 125 if !ok { 126 return ErrNotFound 127 } 128 129 elem.refCount++ 130 131 return nil 132 } 133 134 // Decr decrements reference count of an element 135 func (rt *RefTracker) Decr(key string) error { 136 rt.elemMux.Lock() 137 defer rt.elemMux.Unlock() 138 139 elem, ok := rt.elements[key] 140 if !ok { 141 return ErrNotFound 142 } 143 144 elem.refCount-- 145 146 if elem.refCount < 0 { 147 panic("negative reference count in RefTracker element!") 148 } 149 150 if elem.refCount == 0 { 151 elem.lastReleased = time.Now() 152 } 153 154 return nil 155 } 156 157 // Close stops patrol loop 158 func (rt *RefTracker) Close() { 159 rt.stopOnce.Do(func() { 160 close(rt.stop) 161 }) 162 }