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
|
package fdtrace
import (
"bytes"
"fmt"
"os"
"runtime"
"sync"
"sync/atomic"
)
// foundLeak is atomic since the GC may collect objects in parallel.
var foundLeak atomic.Bool
func onLeakFD(fs *runtime.Frames) {
foundLeak.Store(true)
fmt.Fprintln(os.Stderr, "leaked fd created at:")
fmt.Fprintln(os.Stderr, formatFrames(fs))
}
// fds is a registry of all file descriptors wrapped into sys.fds that were
// created while an fd tracer was active.
var fds *sync.Map // map[int]*runtime.Frames
// TraceFD associates raw with the current execution stack.
//
// skip controls how many entries of the stack the function should skip.
func TraceFD(raw int, skip int) {
if fds == nil {
return
}
// Attempt to store the caller's stack for the given fd value.
// Panic if fds contains an existing stack for the fd.
old, exist := fds.LoadOrStore(raw, callersFrames(skip))
if exist {
f := old.(*runtime.Frames)
panic(fmt.Sprintf("found existing stack for fd %d:\n%s", raw, formatFrames(f)))
}
}
// ForgetFD removes any existing association for raw.
func ForgetFD(raw int) {
if fds != nil {
fds.Delete(raw)
}
}
// LeakFD indicates that raw was leaked.
//
// Calling the function with a value that was not passed to [TraceFD] before
// is undefined.
func LeakFD(raw int) {
if fds == nil {
return
}
// Invoke the fd leak callback. Calls LoadAndDelete to guarantee the callback
// is invoked at most once for one sys.FD allocation, runtime.Frames can only
// be unwound once.
f, ok := fds.LoadAndDelete(raw)
if ok {
onLeakFD(f.(*runtime.Frames))
}
}
// flushFrames removes all elements from fds and returns them as a slice. This
// deals with the fact that a runtime.Frames can only be unwound once using
// Next().
func flushFrames() []*runtime.Frames {
var frames []*runtime.Frames
fds.Range(func(key, value any) bool {
frames = append(frames, value.(*runtime.Frames))
fds.Delete(key)
return true
})
return frames
}
func callersFrames(skip int) *runtime.Frames {
c := make([]uintptr, 32)
// Skip runtime.Callers and this function.
i := runtime.Callers(skip+2, c)
if i == 0 {
return nil
}
return runtime.CallersFrames(c)
}
// formatFrames formats a runtime.Frames as a human-readable string.
func formatFrames(fs *runtime.Frames) string {
var b bytes.Buffer
for {
f, more := fs.Next()
b.WriteString(fmt.Sprintf("\t%s+%#x\n\t\t%s:%d\n", f.Function, f.PC-f.Entry, f.File, f.Line))
if !more {
break
}
}
return b.String()
}
|