Reputation: 6297
Consider the following code snippet:
func a(fd int) {
file := os.NewFile(uintptr(fd), "")
defer func() {
if err := file.Close(); err != nil {
fmt.Printf("%v", err)
}
}
This piece of code is legit, and will work OK. Files will be closed upon returning from a()
However, The following will not work correctly:
func a(fd int) {
file := os.NewFile(uintptr(fd), "")
defer func() {
if err := syscall.Close(int(file.Fd()); err != nil {
fmt.Printf("%v", err)
}
}
The error that will be received, occasionally, will be bad file descriptor
, due to the fact of NewFile setting a finalizer
which, during garbage collection, will close the file itself.
Whats unclear to me, is that the deferred function still has a reference to the file, so theoretically, it shouldn't be garbage collected yet. So why is golang runtime behaves that way?
Upvotes: 2
Views: 609
Reputation: 2465
the problems of the code is after file.Fd()
return, file
is unreachable, so file
may be close by the finalizer(garbage collected).
according to runtime.SetFinalizer:
For example, if p points to a struct that contains a file descriptor d, and p has a finalizer that closes that file descriptor, and if the last use of p in a function is a call to syscall.Write(p.d, buf, size), then p may be unreachable as soon as the program enters syscall.Write. The finalizer may run at that moment, closing p.d, causing syscall.Write to fail because it is writing to a closed file descriptor (or, worse, to an entirely different file descriptor opened by a different goroutine). To avoid this problem, call runtime.KeepAlive(p) after the call to syscall.Write.
runtime.KeepAlive usage:
KeepAlive marks its argument as currently reachable. This ensures that the object is not freed, and its finalizer is not run, before the point in the program where KeepAlive is called.
func a(fd int) {
file := os.NewFile(uintptr(fd), "")
defer func() {
if err := syscall.Close(int(file.Fd()); err != nil {
fmt.Printf("%v", err)
}
runtime.KeepAlive(file)
}()
}
Upvotes: 5