2018-04-05, David Crawshaw
For background, see my last post on why in general finalizers do not work.
We cannot use an object finalizer for resource management, because finalizers are called at some unpredictable distant time long after resources need to be reclaimed.
However, a finalizer does provide us with a bound on when a managed resource needs to have been released. If we reach an object finalizer, and a manually managed resource has not been freed, then there is a bug in the program.
So we can use finalizers to detect resource leaks.
package db
func Open(path string, flags OpenFlags) (*Conn, error) {
// ...
runtime.SetFinalizer(conn, func(conn *Conn) {
panic("open db connection never closed")
})
return conn, nil
}
func (c *Conn) Close() {
// ...
runtime.SetFinalizer(conn, nil) // clear finalizer
}
This is a sharp-edged finalizer. Misuse the resource and it will cut your program short.
I suspect this kind of aggressive finalizer is off-putting to many, who view resource management something nice to have. But there are many programs for which correct resource management is vital. Leaking a resource can leave to unsuspecting crashes, or data loss. For people in similar situations, you may want to consider a panicing finalizer.
One big problem with the code above is the error message is rubbish. You leaked something. OK, great. Got any details?
Ideally the error would point at exactly where we need to release the resource, but this is a hard problem. One cheap and easy alternative is to point to where the resource was originally acquired, which is straightforward:
_, file, line, _ := runtime.Caller(1)
runtime.SetFinalizer(conn, func(conn *Conn) {
panic(fmt.Sprintf("%s:%d: db conn not closed", file, line))
})
This prints the file name and line number of where the connection was created, which is often useful in tracing a leaked resource.
Because I have found myself ignoring logs, time and again.
While working on an sqlite wrapper package most of the leaked
resources I encountered were in the tests of programs using the
package.
A log line in the middle of go test
will never be seen.
A panic will.