go / expert
Snippet
Resource Lifecycle Guards Using runtime.KeepAlive
When combining object finalizers (`runtime.SetFinalizer`) with raw handles (such as file descriptors or C pointers), the Go GC can reclaim a container struct mid-method if fields inside it are copied out and the main struct is no longer referenced. `runtime.KeepAlive` explicitly guarantees that the object remains unreachable for garbage collection until that point in execution.
snippet.go
go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package mainimport ("fmt""runtime""syscall")type FileDescriptorHolder struct {fd int}func NewHolder(fd int) *FileDescriptorHolder {h := &FileDescriptorHolder{fd: fd}runtime.SetFinalizer(h, func(obj *FileDescriptorHolder) {syscall.Close(obj.fd)})return h}func (h *FileDescriptorHolder) Read(b []byte) (int, error) {// If GC runs right here, 'h' could be collected before syscall completes!n, err := syscall.Read(h.fd, b)// Expressly mark 'h' reachable until after the system call finishesruntime.KeepAlive(h)return n, err}func main() {holder := NewHolder(0)buf := make([]byte, 16)_, _ = holder.Read(buf)fmt.Println("Read finished safely")}
Breakdown
1
runtime.SetFinalizer(h, func(obj *FileDescriptorHolder) {
Registers an automatic cleanup hook triggered when the garbage collector determines the object is unreachable.
2
runtime.KeepAlive(h)
Ensures the compiler and GC retain 'h' as reachable through the execution of the low-level read syscall.