summaryrefslogtreecommitdiff
path: root/vendor/codeberg.org/gruf/go-mutexes/map.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/codeberg.org/gruf/go-mutexes/map.go')
-rw-r--r--vendor/codeberg.org/gruf/go-mutexes/map.go466
1 files changed, 0 insertions, 466 deletions
diff --git a/vendor/codeberg.org/gruf/go-mutexes/map.go b/vendor/codeberg.org/gruf/go-mutexes/map.go
deleted file mode 100644
index c0f740eec..000000000
--- a/vendor/codeberg.org/gruf/go-mutexes/map.go
+++ /dev/null
@@ -1,466 +0,0 @@
-package mutexes
-
-import (
- "runtime"
- "sync"
- "sync/atomic"
-)
-
-const (
- // possible lock types.
- lockTypeRead = uint8(1) << 0
- lockTypeWrite = uint8(1) << 1
- lockTypeMap = uint8(1) << 2
-
- // possible mutexmap states.
- stateUnlockd = uint8(0)
- stateRLocked = uint8(1)
- stateLocked = uint8(2)
- stateInUse = uint8(3)
-
- // default values.
- defaultWake = 1024
-)
-
-// acquireState attempts to acquire required map state for lockType.
-func acquireState(state uint8, lt uint8) (uint8, bool) {
- switch state {
- // Unlocked state
- // (all allowed)
- case stateUnlockd:
-
- // Keys locked, no state lock.
- // (don't allow map locks)
- case stateInUse:
- if lt&lockTypeMap != 0 {
- return 0, false
- }
-
- // Read locked
- // (only allow read locks)
- case stateRLocked:
- if lt&lockTypeRead == 0 {
- return 0, false
- }
-
- // Write locked
- // (none allowed)
- case stateLocked:
- return 0, false
-
- // shouldn't reach here
- default:
- panic("unexpected state")
- }
-
- switch {
- // If unlocked and not a map
- // lock request, set in use
- case lt&lockTypeMap == 0:
- if state == stateUnlockd {
- state = stateInUse
- }
-
- // Set read lock state
- case lt&lockTypeRead != 0:
- state = stateRLocked
-
- // Set write lock state
- case lt&lockTypeWrite != 0:
- state = stateLocked
-
- default:
- panic("unexpected lock type")
- }
-
- return state, true
-}
-
-// MutexMap is a structure that allows read / write locking key, performing
-// as you'd expect a map[string]*sync.RWMutex to perform. The differences
-// being that the entire map can itself be read / write locked, it uses memory
-// pooling for the mutex (not quite) structures, and it is self-evicting. The
-// core configurations of maximum no. open locks and wake modulus* are user
-// definable.
-//
-// * The wake modulus is the number that the current number of open locks is
-// modulused against to determine how often to notify sleeping goroutines.
-// These are goroutines that are attempting to lock a key / whole map and are
-// awaiting a permissible state (.e.g no key write locks allowed when the
-// map is read locked).
-type MutexMap struct {
- qpool pool
- queue []*sync.Mutex
-
- mumap map[string]*rwmutex
- mpool pool
- evict []*rwmutex
-
- count int32
- maxmu int32
- wake int32
-
- mapmu sync.Mutex
- state uint8
-}
-
-// NewMap returns a new MutexMap instance with provided max no. open mutexes.
-func NewMap(max, wake int32) MutexMap {
- // Determine wake mod.
- if wake < 1 {
- wake = defaultWake
- }
-
- // Determine max no. mutexes
- if max < 1 {
- procs := runtime.GOMAXPROCS(0)
- max = wake * int32(procs)
- }
-
- return MutexMap{
- qpool: pool{
- alloc: func() interface{} {
- return &sync.Mutex{}
- },
- },
- mumap: make(map[string]*rwmutex, max),
- mpool: pool{
- alloc: func() interface{} {
- return &rwmutex{}
- },
- },
- maxmu: max,
- wake: wake,
- }
-}
-
-// MAX sets the MutexMap max open locks and wake modulus, returns current values.
-// For values less than zero defaults are set, and zero is non-op.
-func (mm *MutexMap) SET(max, wake int32) (int32, int32) {
- mm.mapmu.Lock()
-
- switch {
- // Set default wake
- case wake < 0:
- mm.wake = defaultWake
-
- // Set supplied wake
- case wake > 0:
- mm.wake = wake
- }
-
- switch {
- // Set default max
- case max < 0:
- procs := runtime.GOMAXPROCS(0)
- mm.maxmu = wake * int32(procs)
-
- // Set supplied max
- case max > 0:
- mm.maxmu = max
- }
-
- // Fetch values
- max = mm.maxmu
- wake = mm.wake
-
- mm.mapmu.Unlock()
- return max, wake
-}
-
-// spinLock will wait (using a mutex to sleep thread) until conditional returns true.
-func (mm *MutexMap) spinLock(cond func() bool) {
- var mu *sync.Mutex
-
- for {
- // Acquire map lock
- mm.mapmu.Lock()
-
- if cond() {
- // Release mu if needed
- if mu != nil {
- mm.qpool.Release(mu)
- }
- return
- }
-
- // Alloc mu if needed
- if mu == nil {
- v := mm.qpool.Acquire()
- mu = v.(*sync.Mutex)
- }
-
- // Queue ourselves
- mm.queue = append(mm.queue, mu)
- mu.Lock()
-
- // Unlock map
- mm.mapmu.Unlock()
-
- // Wait on notify
- mu.Lock()
- mu.Unlock()
- }
-}
-
-// lock will acquire a lock of given type on the 'mutex' at key.
-func (mm *MutexMap) lock(key string, lt uint8) func() {
- var ok bool
- var mu *rwmutex
-
- // Spin lock until returns true
- mm.spinLock(func() bool {
- // Check not overloaded
- if !(mm.count < mm.maxmu) {
- return false
- }
-
- // Attempt to acquire usable map state
- state, ok := acquireState(mm.state, lt)
- if !ok {
- return false
- }
-
- // Update state
- mm.state = state
-
- // Ensure mutex at key
- // is in lockable state
- mu, ok = mm.mumap[key]
- return !ok || mu.CanLock(lt)
- })
-
- // Incr count
- mm.count++
-
- if !ok {
- // No mutex found for key
-
- // Alloc from pool
- v := mm.mpool.Acquire()
- mu = v.(*rwmutex)
- mm.mumap[key] = mu
-
- // Set our key
- mu.key = key
-
- // Queue for eviction
- mm.evict = append(mm.evict, mu)
- }
-
- // Lock mutex
- mu.Lock(lt)
-
- // Unlock map
- mm.mapmu.Unlock()
-
- return func() {
- mm.mapmu.Lock()
- mu.Unlock()
- go mm.cleanup()
- }
-}
-
-// lockMap will lock the whole map under given lock type.
-func (mm *MutexMap) lockMap(lt uint8) {
- // Spin lock until returns true
- mm.spinLock(func() bool {
- // Attempt to acquire usable map state
- state, ok := acquireState(mm.state, lt)
- if !ok {
- return false
- }
-
- // Update state
- mm.state = state
-
- return true
- })
-
- // Incr count
- mm.count++
-
- // State acquired, unlock
- mm.mapmu.Unlock()
-}
-
-// cleanup is performed as the final stage of unlocking a locked key / map state, finally unlocks map.
-func (mm *MutexMap) cleanup() {
- // Decr count
- mm.count--
-
- if mm.count%mm.wake == 0 {
- // Notify queued routines
- for _, mu := range mm.queue {
- mu.Unlock()
- }
-
- // Reset queue
- mm.queue = mm.queue[:0]
- }
-
- if mm.count < 1 {
- // Perform evictions
- for _, mu := range mm.evict {
- key := mu.key
- mu.key = ""
- delete(mm.mumap, key)
- mm.mpool.Release(mu)
- }
-
- // Reset map state
- mm.evict = mm.evict[:0]
- mm.state = stateUnlockd
- mm.mpool.GC()
- mm.qpool.GC()
- }
-
- // Unlock map
- mm.mapmu.Unlock()
-}
-
-// RLockMap acquires a read lock over the entire map, returning a lock state for acquiring key read locks.
-// Please note that the 'unlock()' function will block until all keys locked from this state are unlocked.
-func (mm *MutexMap) RLockMap() *LockState {
- mm.lockMap(lockTypeRead | lockTypeMap)
- return &LockState{
- mmap: mm,
- ltyp: lockTypeRead,
- }
-}
-
-// LockMap acquires a write lock over the entire map, returning a lock state for acquiring key read/write locks.
-// Please note that the 'unlock()' function will block until all keys locked from this state are unlocked.
-func (mm *MutexMap) LockMap() *LockState {
- mm.lockMap(lockTypeWrite | lockTypeMap)
- return &LockState{
- mmap: mm,
- ltyp: lockTypeWrite,
- }
-}
-
-// RLock acquires a mutex read lock for supplied key, returning an RUnlock function.
-func (mm *MutexMap) RLock(key string) (runlock func()) {
- return mm.lock(key, lockTypeRead)
-}
-
-// Lock acquires a mutex write lock for supplied key, returning an Unlock function.
-func (mm *MutexMap) Lock(key string) (unlock func()) {
- return mm.lock(key, lockTypeWrite)
-}
-
-// LockState represents a window to a locked MutexMap.
-type LockState struct {
- wait sync.WaitGroup
- mmap *MutexMap
- done uint32
- ltyp uint8
-}
-
-// Lock: see MutexMap.Lock() definition. Will panic if map only read locked.
-func (st *LockState) Lock(key string) (unlock func()) {
- return st.lock(key, lockTypeWrite)
-}
-
-// RLock: see MutexMap.RLock() definition.
-func (st *LockState) RLock(key string) (runlock func()) {
- return st.lock(key, lockTypeRead)
-}
-
-// lock: see MutexMap.lock() definition.
-func (st *LockState) lock(key string, lt uint8) func() {
- st.wait.Add(1) // track lock
-
- if atomic.LoadUint32(&st.done) == 1 {
- panic("called (r)lock on unlocked state")
- } else if lt&lockTypeWrite != 0 &&
- st.ltyp&lockTypeWrite == 0 {
- panic("called lock on rlocked map")
- }
-
- var ok bool
- var mu *rwmutex
-
- // Spin lock until returns true
- st.mmap.spinLock(func() bool {
- // Check not overloaded
- if !(st.mmap.count < st.mmap.maxmu) {
- return false
- }
-
- // Ensure mutex at key
- // is in lockable state
- mu, ok = st.mmap.mumap[key]
- return !ok || mu.CanLock(lt)
- })
-
- // Incr count
- st.mmap.count++
-
- if !ok {
- // No mutex found for key
-
- // Alloc from pool
- v := st.mmap.mpool.Acquire()
- mu = v.(*rwmutex)
- st.mmap.mumap[key] = mu
-
- // Set our key
- mu.key = key
-
- // Queue for eviction
- st.mmap.evict = append(st.mmap.evict, mu)
- }
-
- // Lock mutex
- mu.Lock(lt)
-
- // Unlock map
- st.mmap.mapmu.Unlock()
-
- return func() {
- st.mmap.mapmu.Lock()
- mu.Unlock()
- go st.mmap.cleanup()
- st.wait.Add(-1)
- }
-}
-
-// UnlockMap will close this state and release the currently locked map.
-func (st *LockState) UnlockMap() {
- if !atomic.CompareAndSwapUint32(&st.done, 0, 1) {
- panic("called unlockmap on expired state")
- }
- st.wait.Wait()
- st.mmap.mapmu.Lock()
- go st.mmap.cleanup()
-}
-
-// rwmutex is a very simple *representation* of a read-write
-// mutex, though not one in implementation. it works by
-// tracking the lock state for a given map key, which is
-// protected by the map's mutex.
-type rwmutex struct {
- rcnt uint32
- lock uint8
- key string
-}
-
-func (mu *rwmutex) CanLock(lt uint8) bool {
- return mu.lock == 0 ||
- (mu.lock&lockTypeRead != 0 && lt&lockTypeRead != 0)
-}
-
-func (mu *rwmutex) Lock(lt uint8) {
- mu.lock = lt
- if lt&lockTypeRead != 0 {
- mu.rcnt++
- }
-}
-
-func (mu *rwmutex) Unlock() {
- mu.rcnt--
- if mu.rcnt == 0 {
- mu.lock = 0
- }
-}