Go 1.7 beta 1 was released this morning, here is the release notes draft of Go 1.7. A new function KeepAlive
was added to the package runtime
. The doc of runtime.KeepAlive
has given an example:
type File struct { d int }
d, err := syscall.Open("/file/path", syscall.O_RDONLY, 0)
// ... do something if err != nil ...
p := &FILE{d}
runtime.SetFinalizer(p, func(p *File) { syscall.Close(p.d) })
var buf [10]byte
n, err := syscall.Read(p.d, buf[:])
// Ensure p is not finalized until Read returns.
runtime.KeepAlive(p)
// No more uses of p after this point.
The doc of runtime.SetFinalizer
has also given an explanation about runtime.KeepAlive
:
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.
What confused me is that the variable p
has not left its life scope yet, why will it be unreachable? Does that mean that a variable will be unreachable if only there is no use of it in the following code, no matter whether it is in its life scope?
A variable becomes unreachable when the runtime detects that the Go code cannot reach a point where that variable is referenced again.
In the example you posted, a syscall.Open()
is used to open a file. The returned file descriptor (which is just an int
value) is "wrapped" in a struct
. Then a finalizer is attached to this struct value that closes the file descriptor. Now when this struct value becomes unreachable, its finalizer may be run at any moment, and the closing / invalidation / re-using of the file descriptor could cause unexpected behavior or errors in the execution of the Read()
syscall.
The last use of this struct value p
in Go code is when syscall.Read()
is invoked (and the file descriptor p.d
is passed to it). The implementation of the syscall will use that file descriptor after the initiation of syscall.Read()
, it may do so up until syscall.Read()
returns. But this use of the file descriptor is "independent" of the Go code.
So the struct value p
is not used during the execution of the syscall, and the syscall blocks the Go code until it returns. Which means the Go runtime is allowed to mark p
as unreachable during the execution of Read()
(before Read()
returns), or even before its actual execution begins (because p
is only used to provide the arguments to call Read()
.
Hence the call to runtime.KeepAlive()
: since this call is after the syscall.Read()
and it references the variable p
, the Go runtime is not allowed to mark p
unreachable before Read()
returns, because this is after the Read()
call.
Note that you could use other constructs to "keep p
alive", e.g. _ = p
or returning it. runtime.KeepAlive()
does nothing magical in the background, its implementation is:
func KeepAlive(interface{}) {}
runtime.KeepAlive()
does provide a much better alternative because:
p
alive (to prevent runs of Finalizers)._ = p
might get "optimized" out by future compilers, but not runtime.KeepAlive()
calls.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With