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  }