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.go235
1 files changed, 0 insertions, 235 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 4b721cec4..000000000
--- a/vendor/codeberg.org/gruf/go-mutexes/map.go
+++ /dev/null
@@ -1,235 +0,0 @@
-package mutexes
-
-import (
- "sync"
- "sync/atomic"
- "unsafe"
-
- "codeberg.org/gruf/go-mempool"
-)
-
-const (
- // possible lock types.
- lockTypeRead = uint8(1) << 0
- lockTypeWrite = uint8(1) << 1
-)
-
-// MutexMap is a structure that allows read / write locking
-// per key, performing as you'd expect a map[string]*RWMutex
-// to perform, without you needing to worry about deadlocks
-// between competing read / write locks and the map's own mutex.
-// It uses memory pooling for the internal "mutex" (ish) types
-// and performs self-eviction of keys.
-//
-// Under the hood this is achieved using a single mutex for the
-// map, state tracking for individual keys, and some sync.Cond{}
-// like structures for sleeping / awaking awaiting goroutines.
-type MutexMap struct {
- mapmu sync.Mutex
- mumap hashmap
- mupool mempool.UnsafePool
-}
-
-// checkInit ensures MutexMap is initialized (UNSAFE).
-func (mm *MutexMap) checkInit() {
- if mm.mumap.m == nil {
- mm.mumap.init(0)
- mm.mupool.DirtyFactor = 256
- }
-}
-
-// Lock acquires a write lock on key in map, returning unlock function.
-func (mm *MutexMap) Lock(key string) func() {
- return mm.lock(key, lockTypeWrite)
-}
-
-// RLock acquires a read lock on key in map, returning runlock function.
-func (mm *MutexMap) RLock(key string) func() {
- return mm.lock(key, lockTypeRead)
-}
-
-func (mm *MutexMap) lock(key string, lt uint8) func() {
- // Perform first map lock
- // and check initialization
- // OUTSIDE the main loop.
- mm.mapmu.Lock()
- mm.checkInit()
-
- for {
- // Check map for mutex.
- mu := mm.mumap.Get(key)
-
- if mu == nil {
- // Allocate mutex.
- mu = mm.acquire()
- mm.mumap.Put(key, mu)
- }
-
- if !mu.Lock(lt) {
- // Wait on mutex unlock, after
- // immediately relocking map mu.
- mu.WaitRelock()
- continue
- }
-
- // Done with map.
- mm.mapmu.Unlock()
-
- // Return mutex unlock function.
- return func() { mm.unlock(key, mu) }
- }
-}
-
-func (mm *MutexMap) unlock(key string, mu *rwmutex) {
- // Get map lock.
- mm.mapmu.Lock()
-
- // Unlock mutex.
- if !mu.Unlock() {
-
- // Fast path. Mutex still
- // used so no map change.
- mm.mapmu.Unlock()
- return
- }
-
- // Mutex fully unlocked
- // with zero waiters. Self
- // evict and release it.
- mm.mumap.Delete(key)
- mm.release(mu)
-
- // Check if compaction
- // needed.
- mm.mumap.Compact()
-
- // Done with map.
- mm.mapmu.Unlock()
-}
-
-// acquire will acquire mutex from memory pool, or alloc new.
-func (mm *MutexMap) acquire() *rwmutex {
- if ptr := mm.mupool.Get(); ptr != nil {
- return (*rwmutex)(ptr)
- }
- mu := new(rwmutex)
- mu.c.L = &mm.mapmu
- return mu
-}
-
-// release will release given mutex to memory pool.
-func (mm *MutexMap) release(mu *rwmutex) {
- ptr := unsafe.Pointer(mu)
- mm.mupool.Put(ptr)
-}
-
-// rwmutex represents a RW mutex when used correctly within
-// a MapMutex. It should ONLY be access when protected by
-// the outer map lock, except for the 'notifyList' which is
-// a runtime internal structure borrowed from the sync.Cond{}.
-//
-// this functions very similarly to a sync.Cond{}, but with
-// lock state tracking, and returning on 'Broadcast()' whether
-// any goroutines were actually awoken. it also has a less
-// confusing API than sync.Cond{} with the outer locking
-// mechanism we use, otherwise all Cond{}.L would reference
-// the same outer map mutex.
-type rwmutex struct {
- c sync.Cond // 'trigger' mechanism
- l int32 // no. locks
- t uint8 // lock type
-}
-
-// Lock will lock the mutex for given lock type, in the
-// sense that it will update the internal state tracker
-// accordingly. Return value is true on successful lock.
-func (mu *rwmutex) Lock(lt uint8) bool {
- switch mu.t {
- case lockTypeRead:
- // already read locked,
- // only permit more reads.
- if lt != lockTypeRead {
- return false
- }
-
- case lockTypeWrite:
- // already write locked,
- // no other locks allowed.
- return false
-
- default:
- // Fully unlocked,
- // set incoming type.
- mu.t = lt
- }
-
- // Update
- // count.
- mu.l++
-
- return true
-}
-
-// Unlock will unlock the mutex, in the sense that it
-// will update the internal state tracker accordingly.
-// On totally unlocked state, it will awaken all
-// sleeping goroutines waiting on this mutex.
-func (mu *rwmutex) Unlock() bool {
- switch mu.l--; {
- case mu.l > 0 && mu.t == lockTypeWrite:
- panic("BUG: multiple writer locks")
- case mu.l < 0:
- panic("BUG: negative lock count")
-
- case mu.l == 0:
- // Fully unlocked.
- mu.t = 0
-
- // NOTE: must remain in
- // sync with runtime.notifyList{}.
- //
- // goexperiment.staticlockranking
- // does change it slightly, but
- // this does not alter the first
- // 2 fields which are all we need.
- type notifyList struct {
- _ uint32
- notify uint32
- // ... other fields
- }
-
- // NOTE: must remain in
- // sync with sync.Cond{}.
- type syncCond struct {
- _ struct{}
- L sync.Locker
- n notifyList
- // ... other fields
- }
-
- // Awake all blocked goroutines and check
- // for change in the last notified ticket.
- cptr := (*syncCond)(unsafe.Pointer(&mu.c))
- before := atomic.LoadUint32(&cptr.n.notify)
- mu.c.Broadcast() // awakes all blocked!
- after := atomic.LoadUint32(&cptr.n.notify)
-
- // If ticket changed, this indicates
- // AT LEAST one goroutine was awoken.
- //
- // (before != after) => (waiters > 0)
- // (before == after) => (waiters = 0)
- return (before == after)
-
- default:
- // i.e. mutex still
- // locked by others.
- return false
- }
-}
-
-// WaitRelock expects a mutex to be passed in, already in the
-// locked state. It incr the notifyList waiter count before
-// unlocking the outer mutex and blocking on notifyList wait.
-// On awake it will decr wait count and relock outer mutex.
-func (mu *rwmutex) WaitRelock() { mu.c.Wait() }