If a resource requires explicit closing but finalization is not an option due to performance/limited number of resources/ideology (os.File), it still can help with resource leak detection in debug mode.
package main
import "co"
import "runtime"
import "time"
func foo() {
rc := co.MakeResourceCache(1, 2, 0, nil, nil)
...
// forget to call rc.Close()
}
func main() {
foo()
runtime.GC()
time.Sleep(1e8)
}
$ ./test
co.ResourceCache leak at:
main.foo+0x45 /home/dvyukov/go_xadd/src/pkg/co/test.go:8
main.foo()
main.main+0x27 /home/dvyukov/go_xadd/src/pkg/co/test.go:14
main.main()
runtime.mainstart+0xf /home/dvyukov/go_xadd/src/pkg/runtime/amd64/asm.s:79
runtime.mainstart()
runtime.goexit+0x0 /home/dvyukov/go_xadd/src/pkg/runtime/proc.c:244
runtime.goexit()
type ResourceCache struct {
...
}
var debug = os.Getenv("GODEBUG") != ""
func leakDetectOn(cache *ResourceCache) {
if debug == false {
return
}
callstack := make([]uintptr, 64)
cnt := runtime.Callers(3, callstack)
callstack = callstack[:cnt]
runtime.SetFinalizer(cache, func(cache *ResourceCache) {
print("co.ResourceCache leak at:\n\n")
for i := 0; i != len(callstack); i++ {
pc := callstack[i]
f := runtime.FuncForPC(pc)
if f == nil {
print(unsafe.Pointer(pc), "\n")
continue
}
file, line := f.FileLine(pc)
off := pc - f.Entry()
print(f.Name(), "+", unsafe.Pointer(off), " ", file, ":", line, "\n\t", f.Name(), "()\n")
}
print("\n")
})
}
func leakDetectOff(res interface{}) {
if debug == false {
return
}
runtime.SetFinalizer(res, nil)
}
func MakeResourceCache(low, high, global uint32, ctor func() interface{}, dtor func(interface{})) *ResourceCache {
cache := new(ResourceCache)
...
leakDetectOn(cache)
return cache
}
func (cache *ResourceCache) Close() {
leakDetectOff(cache)
...
}