summaryrefslogtreecommitdiff
path: root/vendor/git.iim.gay/grufwub/go-mutexes/map.go
blob: 1e5b2781ad4272ec87f75697c36f44b3e1fb566c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
package mutexes

import (
	"sync"
)

// MutexMap is a structure that allows having a map of self-evicting mutexes
// by key. You do not need to worry about managing the contents of the map,
// only requesting RLock/Lock for keys, and ensuring to call the returned
// unlock functions.
type MutexMap interface {
	// Lock acquires a mutex lock for supplied key, returning an Unlock function
	Lock(key string) (unlock func())

	// RLock acquires a mutex read lock for supplied key, returning an RUnlock function
	RLock(key string) (runlock func())
}

type mutexMap struct {
	// NOTE:
	// Individual keyed mutexes should ONLY ever
	// be locked within the protection of the outer
	// mapMu lock. If you lock these outside the
	// protection of this, there is a chance for
	// deadlocks

	mus   map[string]RWMutex
	mapMu sync.Mutex
	pool  sync.Pool
}

// NewMap returns a new MutexMap instance based on supplied
// RWMutex allocator function, nil implies use default
func NewMap(newFn func() RWMutex) MutexMap {
	if newFn == nil {
		newFn = NewRW
	}
	return &mutexMap{
		mus:   make(map[string]RWMutex),
		mapMu: sync.Mutex{},
		pool: sync.Pool{
			New: func() interface{} {
				return newFn()
			},
		},
	}
}

func (mm *mutexMap) evict(key string, mu RWMutex) {
	// Acquire map lock
	mm.mapMu.Lock()

	// Toggle mutex lock to
	// ensure it is unused
	unlock := mu.Lock()
	unlock()

	// Delete mutex key
	delete(mm.mus, key)
	mm.mapMu.Unlock()

	// Release to pool
	mm.pool.Put(mu)
}

// GetRLock acquires a mutex read lock for supplied key, returning an RUnlock function
func (mm *mutexMap) RLock(key string) func() {
	return mm.getLock(key, func(mu RWMutex) func() {
		return mu.RLock()
	})
}

// GetLock acquires a mutex lock for supplied key, returning an Unlock function
func (mm *mutexMap) Lock(key string) func() {
	return mm.getLock(key, func(mu RWMutex) func() {
		return mu.Lock()
	})
}

func (mm *mutexMap) getLock(key string, doLock func(RWMutex) func()) func() {
	// Get map lock
	mm.mapMu.Lock()

	// Look for mutex
	mu, ok := mm.mus[key]
	if ok {
		// Lock and return
		// its unlocker func
		unlock := doLock(mu)
		mm.mapMu.Unlock()
		return unlock
	}

	// Note: even though the mutex data structure is
	// small, benchmarking does actually show that pooled
	// alloc of mutexes here is faster

	// Acquire mu + add
	mu = mm.pool.Get().(RWMutex)
	mm.mus[key] = mu

	// Lock mutex + unlock map
	unlockFn := doLock(mu)
	mm.mapMu.Unlock()

	return func() {
		// Unlock mutex
		unlockFn()

		// Release function
		go mm.evict(key, mu)
	}
}