forked from NebulousLabs/Sia
-
Notifications
You must be signed in to change notification settings - Fork 7
/
lock.go
166 lines (141 loc) · 4.23 KB
/
lock.go
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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
package sync
import (
"fmt"
"os"
"runtime"
"sync"
"time"
)
// RWMutex provides locking functions, and an ability to detect and remove
// deadlocks.
type RWMutex struct {
openLocks map[int]lockInfo
openLocksCounter int
openLocksMutex sync.Mutex
callDepth int
maxLockTime time.Duration
mu sync.RWMutex
}
// lockInfo contains information about when and how a lock call was made.
type lockInfo struct {
// When the lock was called.
lockTime time.Time
// Whether it was a RLock or a Lock.
read bool
// Call stack of the caller.
callingFiles []string
callingLines []int
}
// New takes a maxLockTime and returns a lock. The lock will never stay locked
// for more than maxLockTime, instead printing an error and unlocking after
// maxLockTime has passed.
func New(maxLockTime time.Duration, callDepth int) *RWMutex {
rwm := &RWMutex{
openLocks: make(map[int]lockInfo),
maxLockTime: maxLockTime,
callDepth: callDepth,
}
go rwm.threadedDeadlockFinder()
return rwm
}
// threadedDeadlockFinder occasionally freezes the mutexes and scans all open mutexes,
// reporting any that have exceeded their time limit.
func (rwm *RWMutex) threadedDeadlockFinder() {
for {
rwm.openLocksMutex.Lock()
for id, info := range rwm.openLocks {
// Check if the lock has been held for longer than 'maxLockTime'.
if time.Now().Sub(info.lockTime) > rwm.maxLockTime {
str := fmt.Sprintf("A lock was held for too long, id '%v'. Call stack:\n", id)
for i := 0; i <= rwm.callDepth; i++ {
str += fmt.Sprintf("\tFile: '%v:%v'\n", info.callingFiles[i], info.callingLines[i])
}
os.Stderr.WriteString(str)
os.Stderr.Sync()
// Undo the deadlock and delete the entry from the map.
if info.read {
rwm.mu.RUnlock()
} else {
rwm.mu.Unlock()
}
delete(rwm.openLocks, id)
}
}
rwm.openLocksMutex.Unlock()
time.Sleep(rwm.maxLockTime)
}
}
// safeLock is the generic function for doing safe locking. If the read flag is
// set, then a readlock will be used, otherwise a lock will be used.
func (rwm *RWMutex) safeLock(read bool) int {
// Get the call stack.
var li lockInfo
li.read = read
li.callingFiles = make([]string, rwm.callDepth+1)
li.callingLines = make([]int, rwm.callDepth+1)
for i := 0; i <= rwm.callDepth; i++ {
_, li.callingFiles[i], li.callingLines[i], _ = runtime.Caller(2 + i)
}
// Lock the mutex.
if read {
rwm.mu.RLock()
} else {
rwm.mu.Lock()
}
// Safely register that a lock has been triggered.
rwm.openLocksMutex.Lock()
li.lockTime = time.Now()
id := rwm.openLocksCounter
rwm.openLocks[id] = li
rwm.openLocksCounter++
rwm.openLocksMutex.Unlock()
return id
}
// safeUnlock is the generic function for doing safe unlocking. If the lock had
// to be removed because a deadlock was detected, an error is printed.
func (rwm *RWMutex) safeUnlock(read bool, id int) {
rwm.openLocksMutex.Lock()
defer rwm.openLocksMutex.Unlock()
// Check if a deadlock has been detected and fixed manually.
_, exists := rwm.openLocks[id]
if !exists {
// Get the call stack.
callingFiles := make([]string, rwm.callDepth+1)
callingLines := make([]int, rwm.callDepth+1)
for i := 0; i <= rwm.callDepth; i++ {
_, callingFiles[i], callingLines[i], _ = runtime.Caller(2 + i)
}
fmt.Printf("A lock was held until deadlock, subsequent call to unlock failed. id '%v'. Call stack:\n", id)
for i := 0; i <= rwm.callDepth; i++ {
fmt.Printf("\tFile: '%v:%v'\n", callingFiles[i], callingLines[i])
}
return
}
// Remove the lock and delete the entry from the map.
if read {
rwm.mu.RUnlock()
} else {
rwm.mu.Unlock()
}
delete(rwm.openLocks, id)
}
// RLock will read lock the RWMutex. The return value must be used as input
// when calling RUnlock.
func (rwm *RWMutex) RLock() int {
return rwm.safeLock(true)
}
// RUnlock will read unlock the RWMutex. The return value of calling RLock must
// be used as input.
func (rwm *RWMutex) RUnlock(id int) {
rwm.safeUnlock(true, id)
}
// Lock will lock the RWMutex. The return value must be used as input when
// calling RUnlock.
func (rwm *RWMutex) Lock() int {
return rwm.safeLock(false)
}
// Unlock will unlock the RWMutex. The return value of calling Lock must be
// used as input.
func (rwm *RWMutex) Unlock(id int) {
rwm.safeUnlock(false, id)
}