github.com/qgxpagamentos/mapmutex@v0.0.0-20200716162114-c133e97096b7/mutex.go (about)

     1  package mapmutex
     2  
     3  import (
     4  	"math/rand"
     5  	"sync"
     6  	"time"
     7  )
     8  
     9  // Mutex is the mutex with synchronized map
    10  // it's for reducing unnecessary locks among different keys
    11  type Mutex struct {
    12  	locks     map[interface{}]interface{}
    13  	m         *sync.Mutex
    14  	maxRetry  int
    15  	maxDelay  float64 // in nanosend
    16  	baseDelay float64 // in nanosecond
    17  	factor    float64
    18  	jitter    float64
    19  }
    20  
    21  // TryLock tries to aquire the lock.
    22  func (m *Mutex) TryLock(key interface{}) (gotLock bool) {
    23  	for i := 0; i < m.maxRetry; i++ {
    24  		m.m.Lock()
    25  		if _, ok := m.locks[key]; ok { // if locked
    26  			m.m.Unlock()
    27  			time.Sleep(m.backoff(i))
    28  		} else { // if unlock, lockit
    29  			m.locks[key] = struct{}{}
    30  			m.m.Unlock()
    31  			return true
    32  		}
    33  	}
    34  
    35  	return false
    36  }
    37  
    38  // Unlock unlocks for the key
    39  // please call Unlock only after having aquired the lock
    40  func (m *Mutex) Unlock(key interface{}) {
    41  	m.m.Lock()
    42  	delete(m.locks, key)
    43  	m.m.Unlock()
    44  }
    45  
    46  // borrowed from grpc
    47  func (m *Mutex) backoff(retries int) time.Duration {
    48  	if retries == 0 {
    49  		return time.Duration(m.baseDelay) * time.Nanosecond
    50  	}
    51  	backoff, max := m.baseDelay, m.maxDelay
    52  	for backoff < max && retries > 0 {
    53  		backoff *= m.factor
    54  		retries--
    55  	}
    56  	if backoff > max {
    57  		backoff = max
    58  	}
    59  	backoff *= 1 + m.jitter*(rand.Float64()*2-1)
    60  	if backoff < 0 {
    61  		return 0
    62  	}
    63  	return time.Duration(backoff) * time.Nanosecond
    64  }
    65  
    66  // NewMapMutex returns a mapmutex with default configs
    67  func NewMapMutex() *Mutex {
    68  	return &Mutex{
    69  		locks:     make(map[interface{}]interface{}),
    70  		m:         &sync.Mutex{},
    71  		maxRetry:  200,
    72  		maxDelay:  100000000, // 0.1 second
    73  		baseDelay: 10,        // 10 nanosecond
    74  		factor:    1.1,
    75  		jitter:    0.2,
    76  	}
    77  }
    78  
    79  // NewCustomizedMapMutex returns a customized mapmutex
    80  func NewCustomizedMapMutex(mRetry int, mDelay, bDelay, factor, jitter float64) *Mutex {
    81  	return &Mutex{
    82  		locks:     make(map[interface{}]interface{}),
    83  		m:         &sync.Mutex{},
    84  		maxRetry:  mRetry,
    85  		maxDelay:  mDelay,
    86  		baseDelay: bDelay,
    87  		factor:    factor,
    88  		jitter:    jitter,
    89  	}
    90  }