diff --git a/alloc_test.go b/alloc_test.go new file mode 100644 index 0000000..54077e6 --- /dev/null +++ b/alloc_test.go @@ -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) + } +} diff --git a/deadlock.go b/deadlock.go index 83e581d..a285c75 100644 --- a/deadlock.go +++ b/deadlock.go @@ -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) @@ -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.