Skip to content

Commit

Permalink
Reuse timers with sync.Pool (#31)
Browse files Browse the repository at this point in the history
  • Loading branch information
millfort authored May 30, 2024
1 parent 20e556a commit ed6f7f6
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 48 deletions.
13 changes: 13 additions & 0 deletions alloc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package deadlock

import (
"testing"
)

func BenchmarkCheckDeadlock(b *testing.B) {
ch := make(chan struct{})
close(ch)
for i := 0; i < b.N; i++ {
checkDeadlock(nil, nil, 0, ch)
}
}
117 changes: 69 additions & 48 deletions deadlock.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,54 +182,7 @@ func lock(lockFn func(), ptr interface{}) {
} else {
ch := make(chan struct{})
currentID := goid.Get()
go func() {
for {
t := time.NewTimer(Opts.DeadlockTimeout)
defer t.Stop() // This runs after the losure finishes, but it's OK.
select {
case <-t.C:
lo.mu.Lock()
prev, ok := lo.cur[ptr]
if !ok {
lo.mu.Unlock()
break // Nobody seems to be holding the lock, try again.
}
Opts.mu.Lock()
fmt.Fprintln(Opts.LogBuf, header)
fmt.Fprintln(Opts.LogBuf, "Previous place where the lock was grabbed")
fmt.Fprintf(Opts.LogBuf, "goroutine %v lock %p\n", prev.gid, ptr)
printStack(Opts.LogBuf, prev.stack)
fmt.Fprintln(Opts.LogBuf, "Have been trying to lock it again for more than", Opts.DeadlockTimeout)
fmt.Fprintf(Opts.LogBuf, "goroutine %v lock %p\n", currentID, ptr)
printStack(Opts.LogBuf, stack)
stacks := stacks()
grs := bytes.Split(stacks, []byte("\n\n"))
for _, g := range grs {
if goid.ExtractGID(g) == prev.gid {
fmt.Fprintln(Opts.LogBuf, "Here is what goroutine", prev.gid, "doing now")
Opts.LogBuf.Write(g)
fmt.Fprintln(Opts.LogBuf)
}
}
lo.other(ptr)
if Opts.PrintAllCurrentGoroutines {
fmt.Fprintln(Opts.LogBuf, "All current goroutines:")
Opts.LogBuf.Write(stacks)
}
fmt.Fprintln(Opts.LogBuf)
if buf, ok := Opts.LogBuf.(*bufio.Writer); ok {
buf.Flush()
}
Opts.mu.Unlock()
lo.mu.Unlock()
Opts.OnPotentialDeadlock()
<-ch
return
case <-ch:
return
}
}
}()
go checkDeadlock(stack, ptr, currentID, ch)
lockFn()
postLock(stack, ptr)
close(ch)
Expand All @@ -238,6 +191,74 @@ func lock(lockFn func(), ptr interface{}) {
postLock(stack, ptr)
}

var timersPool sync.Pool

func acquireTimer(d time.Duration) *time.Timer {
t, ok := timersPool.Get().(*time.Timer)
if ok {
_ = t.Reset(d)
return t
}
return time.NewTimer(Opts.DeadlockTimeout)
}

func releaseTimer(t *time.Timer) {
if !t.Stop() {
<-t.C
}
timersPool.Put(t)
}

func checkDeadlock(stack []uintptr, ptr interface{}, currentID int64, ch <-chan struct{}) {
t := acquireTimer(Opts.DeadlockTimeout)
defer releaseTimer(t)
for {
select {
case <-t.C:
lo.mu.Lock()
prev, ok := lo.cur[ptr]
if !ok {
lo.mu.Unlock()
break // Nobody seems to be holding the lock, try again.
}
Opts.mu.Lock()
fmt.Fprintln(Opts.LogBuf, header)
fmt.Fprintln(Opts.LogBuf, "Previous place where the lock was grabbed")
fmt.Fprintf(Opts.LogBuf, "goroutine %v lock %p\n", prev.gid, ptr)
printStack(Opts.LogBuf, prev.stack)
fmt.Fprintln(Opts.LogBuf, "Have been trying to lock it again for more than", Opts.DeadlockTimeout)
fmt.Fprintf(Opts.LogBuf, "goroutine %v lock %p\n", currentID, ptr)
printStack(Opts.LogBuf, stack)
stacks := stacks()
grs := bytes.Split(stacks, []byte("\n\n"))
for _, g := range grs {
if goid.ExtractGID(g) == prev.gid {
fmt.Fprintln(Opts.LogBuf, "Here is what goroutine", prev.gid, "doing now")
Opts.LogBuf.Write(g)
fmt.Fprintln(Opts.LogBuf)
}
}
lo.other(ptr)
if Opts.PrintAllCurrentGoroutines {
fmt.Fprintln(Opts.LogBuf, "All current goroutines:")
Opts.LogBuf.Write(stacks)
}
fmt.Fprintln(Opts.LogBuf)
if buf, ok := Opts.LogBuf.(*bufio.Writer); ok {
buf.Flush()
}
Opts.mu.Unlock()
lo.mu.Unlock()
Opts.OnPotentialDeadlock()
<-ch
return
case <-ch:
return
}
t.Reset(Opts.DeadlockTimeout)
}
}

type lockOrder struct {
mu sync.Mutex
cur map[interface{}]stackGID // stacktraces + gids for the locks currently taken.
Expand Down

0 comments on commit ed6f7f6

Please sign in to comment.