github.com/qioalice/ekago/v3@v3.3.2-0.20221202205325-5c262d586ee4/ekafuture/mumap.go (about) 1 // Copyright © 2020. All rights reserved. 2 // Author: Eagle Chen. Modifier: Ilya Stroy. 3 // Original: https://github.com/EagleChen/mapmutex (c133e97) 4 // Contacts: iyuryevich@pm.me, https://github.com/qioalice 5 // License: https://opensource.org/licenses/MIT 6 7 package ekafuture 8 9 import ( 10 "math/rand" 11 "sync" 12 "time" 13 ) 14 15 type ( 16 // Mutex is the mutex with synchronized map 17 // it's for reducing unnecessary locks among different keys 18 MuMap struct { 19 20 // m is the whole MuMap's mutex. Each lock/unlock operation captures this mutex, 21 // but it's not captured when Lock() / TryLock() is waiting for some delay 22 // between key's capturing attempts. 23 m *sync.Mutex 24 25 // key's "mutexes" 26 // map's key it's key; value is a counter. 27 locks map[any]int8 28 29 maxRetry int // how much TryLock() will tries to capture key's "mutex" 30 maxRetryR int // how much RTryLock() will tries to capture key's "mutex" 31 32 maxDelay time.Duration // maximum delay between key's "mutex" capturing attempts 33 baseDelay time.Duration // base delay between key's "mutex" capturing attempts 34 35 factor float64 // multiplier of delay between key's "mutex" capturing attempts 36 jitter float64 // random for factor 37 } 38 ) 39 40 func (m *MuMap) RLock(key any) { 41 m.assertInitialized() 42 m.lock(key, true, true) 43 } 44 45 func (m *MuMap) Lock(key any) { 46 m.assertInitialized() 47 m.lock(key, true, false) 48 } 49 50 func (m *MuMap) RTryLock(key any) (gotLock bool) { 51 m.assertInitialized() 52 return m.lock(key, false, true) 53 } 54 55 // TryLock tries to aquire the lock. 56 func (m *MuMap) TryLock(key any) (gotLock bool) { 57 m.assertInitialized() 58 return m.lock(key, false, false) 59 } 60 61 func (m *MuMap) RUnlock(key any) { 62 m.assertInitialized() 63 m.unlock(key, true) 64 } 65 66 // Unlock unlocks for the key 67 // please call Unlock only after having aquired the lock 68 func (m *MuMap) Unlock(key any) { 69 m.assertInitialized() 70 m.unlock(key, false) 71 } 72 73 // assertInitialized checks whether m is initialized and initialized properly. 74 func (m *MuMap) assertInitialized() { 75 if m == nil || m.locks == nil { 76 panic("MuMap is not initialized properly") 77 } 78 } 79 80 func (m *MuMap) lock(key any, untilSuccess, readOnly bool) (gotLock bool) { 81 82 // First attempt will be done with -1 as attempt's index. 83 // -1 does no delay and no sleep before trying to lock 84 // 0 does m.baseDelay sleeping before trying to lock 85 // any next value does some calculated sleep time before trying to lock 86 87 for i := -1; i < m.maxRetry; i++ { 88 if m.lockIter(key, i, readOnly) { 89 return true 90 } 91 } 92 93 if !untilSuccess { 94 return false 95 } 96 97 for !m.lockIter(key, m.maxRetry, readOnly) { 98 } 99 return true 100 } 101 102 func (m *MuMap) lockIter(key any, attempt int, readOnly bool) (gotLock bool) { 103 104 var sleepDur time.Duration 105 switch { 106 107 case attempt == -1: 108 sleepDur = 0 109 110 case attempt == 0: 111 sleepDur = m.baseDelay 112 113 case attempt >= m.maxRetry: 114 sleepDur = m.maxDelay 115 116 default: 117 backoff, max := float64(m.baseDelay), float64(m.maxDelay) 118 for ; backoff < max && attempt > 0; attempt-- { 119 backoff *= m.factor 120 } 121 if backoff > max { 122 backoff = max 123 } 124 backoff *= 1 + m.jitter*(rand.Float64()*2-1) 125 if backoff < 0 { 126 sleepDur = 0 127 } 128 sleepDur = time.Duration(backoff) 129 } 130 131 time.Sleep(sleepDur) // does nothing if sleepDur == 0 132 133 m.m.Lock() 134 135 keyCounter, alreadyLocked := m.locks[key] 136 switch { 137 138 case readOnly && keyCounter != -1 && keyCounter != 127: 139 // state - RLock or Released, need - RLock 140 m.locks[key]++ 141 gotLock = true 142 143 case !readOnly && !alreadyLocked: 144 // state - Released, need - Lock 145 m.locks[key] = -1 146 gotLock = true 147 } 148 149 m.m.Unlock() 150 151 return gotLock 152 } 153 154 func (m *MuMap) unlock(key any, readOnly bool) { 155 156 m.m.Lock() 157 158 keyCounter, alreadyLocked := m.locks[key] 159 switch { 160 161 case !alreadyLocked: 162 m.m.Unlock() 163 panic("MuMap: Can not unlock unlocked mutex") 164 165 case keyCounter > 0 && !readOnly: 166 // state - RLock, requested - Unlock 167 m.m.Unlock() 168 panic("MuMap: Can not unlock read-locked mutex") 169 170 case keyCounter == -1 && readOnly: 171 // state - Lock, requested - RUnlock 172 m.m.Unlock() 173 panic("MuMap: Can not read-unlock locked mutex") 174 175 case keyCounter == 1 || keyCounter == -1: 176 // state - RLock, requested - RUnlock, no more other RLock calls or 177 // state - Lock, requested - Unlock 178 delete(m.locks, key) 179 180 case keyCounter > 1: 181 // state - RLock, requested - RUnlock, there is at least one more RLock call 182 m.locks[key]-- 183 184 default: 185 m.m.Unlock() 186 panic("MuMap: Unexpected state for unlocking mutex") 187 } 188 189 m.m.Unlock() 190 } 191 192 // NewMapMutex returns a mapmutex with default configs 193 func NewMuMap() *MuMap { 194 return NewMuMapCustom(180, 1*time.Second, 10*time.Nanosecond, 1.1, 0.2) 195 } 196 197 // NewCustomizedMapMutex returns a customized mapmutex 198 func NewMuMapCustom(mRetry int, mDelay, bDelay time.Duration, factor, jitter float64) *MuMap { 199 return &MuMap{ 200 locks: make(map[any]int8), 201 m: &sync.Mutex{}, 202 maxRetry: mRetry, 203 maxDelay: mDelay, 204 baseDelay: bDelay, 205 factor: factor, 206 jitter: jitter, 207 } 208 }