Skip to content

Commit 60b3fe2

Browse files
authored
Merge pull request #2 from deso-protocol/ln/merge-latest-sasha-s-updates
Ln/merge latest sasha s updates
2 parents 4d17582 + 14d9187 commit 60b3fe2

File tree

8 files changed

+370
-78
lines changed

8 files changed

+370
-78
lines changed

.github/workflows/go.yml

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,31 @@ name: Go
22

33
on:
44
push:
5-
branches: [ master ]
5+
branches: [master]
66
pull_request:
7-
branches: [ master ]
7+
branches: [master]
88

99
jobs:
10-
1110
build:
1211
runs-on: ubuntu-latest
12+
strategy:
13+
fail-fast: false
14+
matrix:
15+
go:
16+
- "1.17"
17+
- "1.20"
18+
- "1.22"
19+
- "1.23"
1320
steps:
14-
- uses: actions/checkout@v2
21+
- uses: actions/checkout@v4
1522

16-
- name: Set up Go
17-
uses: actions/setup-go@v2
18-
with:
19-
go-version: 1.17
23+
- name: Set up Go
24+
uses: actions/setup-go@v4
25+
with:
26+
go-version: ${{ matrix.go }}
2027

21-
- name: Build
22-
run: go build -v ./...
28+
- name: Build
29+
run: go build -v ./...
2330

24-
- name: Test
25-
run: go test -v ./...
31+
- name: Test
32+
run: go test -v -bench=. -coverprofile=coverage.txt ./...

alloc_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package deadlock
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func BenchmarkCheckDeadlock(b *testing.B) {
8+
ch := make(chan struct{})
9+
close(ch)
10+
for i := 0; i < b.N; i++ {
11+
checkDeadlock(nil, nil, 0, ch)
12+
}
13+
}

deadlock.go

Lines changed: 84 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ var Opts = struct {
2121
// Would disable lock order based deadlock detection if DisableLockOrderDetection == true.
2222
DisableLockOrderDetection bool
2323
// Waiting for a lock for longer than DeadlockTimeout is considered a deadlock.
24-
// Ignored is DeadlockTimeout <= 0.
24+
// Ignored if DeadlockTimeout <= 0.
2525
DeadlockTimeout time.Duration
2626
// OnPotentialDeadlock is called each time a potential deadlock is detected -- either based on
2727
// lock order or on lock wait time.
@@ -71,6 +71,9 @@ type WaitGroup struct {
7171
sync.WaitGroup
7272
}
7373

74+
// NewCond is a sync.NewCond wrapper
75+
var NewCond = sync.NewCond
76+
7477
// A Mutex is a drop-in replacement for sync.Mutex.
7578
// Performs deadlock detection unless disabled in Opts.
7679
type Mutex struct {
@@ -157,12 +160,12 @@ func (m *RWMutex) RLocker() sync.Locker {
157160
return (*rlocker)(m)
158161
}
159162

160-
func preLock(skip int, p interface{}) {
161-
lo.preLock(skip, p)
163+
func preLock(stack []uintptr, p interface{}) {
164+
lo.preLock(stack, p)
162165
}
163166

164-
func postLock(skip int, p interface{}) {
165-
lo.postLock(skip, p)
167+
func postLock(stack []uintptr, p interface{}) {
168+
lo.postLock(stack, p)
166169
}
167170

168171
func postUnlock(p interface{}) {
@@ -174,65 +177,88 @@ func lock(lockFn func(), ptr interface{}) {
174177
lockFn()
175178
return
176179
}
177-
preLock(4, ptr)
180+
stack := callers(1)
181+
preLock(stack, ptr)
178182
if Opts.DeadlockTimeout <= 0 {
179183
lockFn()
180184
} else {
181185
ch := make(chan struct{})
182-
go func() {
183-
for {
184-
t := time.NewTimer(Opts.DeadlockTimeout)
185-
defer t.Stop() // This runs after the losure finishes, but it's OK.
186-
select {
187-
case <-t.C:
188-
lo.mu.Lock()
189-
prev, ok := lo.cur[ptr]
190-
if !ok {
191-
lo.mu.Unlock()
192-
break // Nobody seems to be holding the lock, try again.
193-
}
194-
Opts.mu.Lock()
195-
fmt.Fprintln(Opts.LogBuf, header)
196-
fmt.Fprintln(Opts.LogBuf, "Previous place where the lock was grabbed")
197-
fmt.Fprintf(Opts.LogBuf, "goroutine %v lock %p\n", prev.gid, ptr)
198-
printStack(Opts.LogBuf, prev.stack)
199-
fmt.Fprintln(Opts.LogBuf, "Have been trying to lock it again for more than", Opts.DeadlockTimeout)
200-
fmt.Fprintf(Opts.LogBuf, "goroutine %v lock %p\n", goid.Get(), ptr)
201-
printStack(Opts.LogBuf, callers(2))
202-
stacks := stacks()
203-
grs := bytes.Split(stacks, []byte("\n\n"))
204-
for _, g := range grs {
205-
if goid.ExtractGID(g) == prev.gid {
206-
fmt.Fprintln(Opts.LogBuf, "Here is what goroutine", prev.gid, "doing now")
207-
Opts.LogBuf.Write(g)
208-
fmt.Fprintln(Opts.LogBuf)
209-
}
210-
}
211-
lo.other(ptr)
212-
if Opts.PrintAllCurrentGoroutines {
213-
fmt.Fprintln(Opts.LogBuf, "All current goroutines:")
214-
Opts.LogBuf.Write(stacks)
215-
}
216-
fmt.Fprintln(Opts.LogBuf)
217-
if buf, ok := Opts.LogBuf.(*bufio.Writer); ok {
218-
buf.Flush()
219-
}
220-
Opts.mu.Unlock()
221-
lo.mu.Unlock()
222-
Opts.OnPotentialDeadlock()
223-
<-ch
224-
return
225-
case <-ch:
226-
return
227-
}
228-
}
229-
}()
186+
currentID := goid.Get()
187+
go checkDeadlock(stack, ptr, currentID, ch)
230188
lockFn()
231-
postLock(4, ptr)
189+
postLock(stack, ptr)
232190
close(ch)
233191
return
234192
}
235-
postLock(4, ptr)
193+
postLock(stack, ptr)
194+
}
195+
196+
var timersPool sync.Pool
197+
198+
func acquireTimer(d time.Duration) *time.Timer {
199+
t, ok := timersPool.Get().(*time.Timer)
200+
if ok {
201+
_ = t.Reset(d)
202+
return t
203+
}
204+
return time.NewTimer(Opts.DeadlockTimeout)
205+
}
206+
207+
func releaseTimer(t *time.Timer) {
208+
if !t.Stop() {
209+
<-t.C
210+
}
211+
timersPool.Put(t)
212+
}
213+
214+
func checkDeadlock(stack []uintptr, ptr interface{}, currentID int64, ch <-chan struct{}) {
215+
t := acquireTimer(Opts.DeadlockTimeout)
216+
defer releaseTimer(t)
217+
for {
218+
select {
219+
case <-t.C:
220+
lo.mu.Lock()
221+
prev, ok := lo.cur[ptr]
222+
if !ok {
223+
lo.mu.Unlock()
224+
break // Nobody seems to be holding the lock, try again.
225+
}
226+
Opts.mu.Lock()
227+
fmt.Fprintln(Opts.LogBuf, header)
228+
fmt.Fprintln(Opts.LogBuf, "Previous place where the lock was grabbed")
229+
fmt.Fprintf(Opts.LogBuf, "goroutine %v lock %p\n", prev.gid, ptr)
230+
printStack(Opts.LogBuf, prev.stack)
231+
fmt.Fprintln(Opts.LogBuf, "Have been trying to lock it again for more than", Opts.DeadlockTimeout)
232+
fmt.Fprintf(Opts.LogBuf, "goroutine %v lock %p\n", currentID, ptr)
233+
printStack(Opts.LogBuf, stack)
234+
stacks := stacks()
235+
grs := bytes.Split(stacks, []byte("\n\n"))
236+
for _, g := range grs {
237+
if goid.ExtractGID(g) == prev.gid {
238+
fmt.Fprintln(Opts.LogBuf, "Here is what goroutine", prev.gid, "doing now")
239+
Opts.LogBuf.Write(g)
240+
fmt.Fprintln(Opts.LogBuf)
241+
}
242+
}
243+
lo.other(ptr)
244+
if Opts.PrintAllCurrentGoroutines {
245+
fmt.Fprintln(Opts.LogBuf, "All current goroutines:")
246+
Opts.LogBuf.Write(stacks)
247+
}
248+
fmt.Fprintln(Opts.LogBuf)
249+
if buf, ok := Opts.LogBuf.(*bufio.Writer); ok {
250+
buf.Flush()
251+
}
252+
Opts.mu.Unlock()
253+
lo.mu.Unlock()
254+
Opts.OnPotentialDeadlock()
255+
<-ch
256+
return
257+
case <-ch:
258+
return
259+
}
260+
t.Reset(Opts.DeadlockTimeout)
261+
}
236262
}
237263

238264
type lockOrder struct {
@@ -265,19 +291,17 @@ func newLockOrder() *lockOrder {
265291
}
266292
}
267293

268-
func (l *lockOrder) postLock(skip int, p interface{}) {
269-
stack := callers(skip)
294+
func (l *lockOrder) postLock(stack []uintptr, p interface{}) {
270295
gid := goid.Get()
271296
l.mu.Lock()
272297
l.cur[p] = stackGID{stack, gid}
273298
l.mu.Unlock()
274299
}
275300

276-
func (l *lockOrder) preLock(skip int, p interface{}) {
301+
func (l *lockOrder) preLock(stack []uintptr, p interface{}) {
277302
if Opts.DisableLockOrderDetection {
278303
return
279304
}
280-
stack := callers(skip)
281305
gid := goid.Get()
282306
l.mu.Lock()
283307
for b, bs := range l.cur {

go.mod

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
11
module github.com/deso-protocol/go-deadlock
22

3-
require github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
4-
5-
go 1.13
3+
require github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ=
2-
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=
1+
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 h1:Dx7Ovyv/SFnMFw3fD4oEoeorXc6saIiQ23LrGLth0Gw=
2+
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=

test.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ set -e
44
echo "" > coverage.txt
55

66
for d in $(go list ./...); do
7-
go test -coverprofile=profile.out -covermode=atomic "$d"
7+
go test -bench=. -coverprofile=profile.out -covermode=atomic "$d"
88
if [ -f profile.out ]; then
99
cat profile.out >> coverage.txt
1010
rm profile.out

trylock.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// +build go1.18
2+
3+
package deadlock
4+
5+
// TryLock tries to lock the mutex.
6+
// Returns false if the lock is already in use, true otherwise.
7+
func (m *Mutex) TryLock() bool {
8+
return trylock(m.mu.TryLock, m)
9+
}
10+
11+
// TryLock tries to lock rw for writing.
12+
// Returns false if the lock is already locked for reading or writing, true otherwise.
13+
func (m *RWMutex) TryLock() bool {
14+
return trylock(m.mu.TryLock, m)
15+
}
16+
17+
// TryRLock tries to lock rw for reading.
18+
// Returns false if the lock is already locked for writing, true otherwise.
19+
func (m *RWMutex) TryRLock() bool {
20+
return trylock(m.mu.TryRLock, m)
21+
}
22+
23+
// trylock can not deadlock, so there is no deadlock detection.
24+
// lock ordering is still supported by calling into preLock/postLock,
25+
// and in failed attempt into postUnlock to unroll the state added by preLock.
26+
func trylock(lockFn func() bool, ptr interface{}) bool {
27+
if Opts.Disable {
28+
return lockFn()
29+
}
30+
stack := callers(1)
31+
preLock(stack, ptr)
32+
ret := lockFn()
33+
if ret {
34+
postLock(stack, ptr)
35+
} else {
36+
postUnlock(ptr)
37+
}
38+
return ret
39+
}

0 commit comments

Comments
 (0)