-
Notifications
You must be signed in to change notification settings - Fork 716
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
internal/fdtrace: allow tracing of sys package
The sys package tests don't benefit from our fd leak tracking due to an import cycle between fdtrace and sys. Move the implementation into fdtrace and call fdtrace.TestMain from sys. This lays the ground work to add platform specific debug aids to fdtrace later on. Signed-off-by: Lorenz Bauer <lmb@isovalent.com>
- Loading branch information
Showing
7 changed files
with
145 additions
and
145 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,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() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package fdtrace | ||
|
||
import ( | ||
"os" | ||
"sync" | ||
) | ||
|
||
type testingM interface { | ||
Run() int | ||
} | ||
|
||
// TestMain runs m with fd tracing enabled. | ||
// | ||
// The function calls [os.Exit] and does not return. | ||
func TestMain(m testingM) { | ||
fds = new(sync.Map) | ||
|
||
ret := m.Run() | ||
|
||
if fs := flushFrames(); len(fs) != 0 { | ||
for _, f := range fs { | ||
onLeakFD(f) | ||
} | ||
} | ||
|
||
if foundLeak.Load() { | ||
ret = 99 | ||
} | ||
|
||
os.Exit(ret) | ||
} |