Curious about runtime.KeepAlive usage

I’m sorry for my previous response. What I should have said was, consider this function

func f() {
        x := 1
        y := x * x
        if y > 2 {
               panic("wat")
        }
}

From the point of view of the compiler, the storage occupied by x is considered dead once the assignment to y is made as x is not referenced anywhere else in the function.

This is a simple example which is straight forward, but consider this

func g() {
    x := make([]byte, 2^24)
    y := x[0]
    if y > 0 {
            panic("wat")
    }
}

Most people would expect that x would be garbage collected before the end of the function because it is no longer referenced. But consider this situation

func h() {
       x, _ := os.Open("somefile")
       fd := x.Fd()
       // use fd in some kind of select or poll operation inside this function.
}

If you’ve follow the logic of what I’ve said up to this point, you would expect x to be dead after the assignment to fd, but we know that *os.File values have a finaliser attached to them, which will be invoked soon after x goes out of scope at the end of the second line.

When that happens, the finalizer will close the file descriptor that fd references. If you’re lucky you’ll get an error about writing to a closed file. If you’re unlucky, another goroutine will open a different file, and receive the same file descriptor number causing file corruption.

The workaround is to use runtime.KeepAlive to keep the reference in x live for the duration of the function.

The moral of the story is finalisers are terrible, and adding one to *os.File was a mistake.

3 Likes