Curious about runtime.KeepAlive usage

I’ve been doing some reading about runtime.KeepAlive and I understand that it’s commonly used with SetFinalizer to ensure a resource isn’t GC’d until post-KeepAlive call.

That said, I guess I’m still confused at when one would use it. Given how few times it’s actually used in golang’s source, I don’t have too many examples to look at.

It’d be great if someone could explain why runtime.KeepAlive() is used here (in init) https://github.com/golang/go/blob/master/src/net/fd_poll_runtime.go#L37 but not in setDeadlineImpl() https://github.com/golang/go/blob/master/src/net/fd_poll_runtime.go#L124 when they’re both dealing with *netFD.

Thanks!

You shouldn’t need to use it. Seriously, if you are using it at all then you have made a design error.

The purpose of keep alive is to ensure that the garbage collector does not collect a value which is no longer referenced inside a function. The usual cases where this is a problem is if you have obtained the file descriptor to an underlying os.File, or you have passed memory to cgo which was allocated inside the same function.

I haven’t used it, just curious about some of the inner-workings of Golang.

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

Thanks for the great explanation Dave.

One follow up question:

Since you just indicated a place where not having KeepAlive would cause further issues, why wouldn’t one use it given that the finalizers were implemented.

… Now I see the glaring issue with finalizers.

Edit after thinking about it for a minute:
Wait, would you not need the KeepAlive() because ideally you’d have a defer x.Close() and thus a later reference to ‘x’ ?

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.