runtime: AddCleanup func is never executed if ptr points to struct with a single numeric field
Closed this issue · 1 comments
Go version
go version go1.25.3 linux/amd64
Output of go env in your module/workspace:
AR='ar'
CC='x86_64-linux-gnu-gcc'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='x86_64-linux-gnu-g++'
GCCGO='/usr/bin/gccgo'
GO111MODULE=''
GOAMD64='v1'
GOARCH='amd64'
GOAUTH='netrc'
GOBIN=''
GOCACHE='/home/ext_zdilap/.cache/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/home/ext_zdilap/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build2719480727=/tmp/go-build -gno-record-gcc-switches'
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMOD='/home/ext_zdilap/stuff/go/test/go.mod'
GOMODCACHE='/home/ext_zdilap/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/home/ext_zdilap/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/lib/go-1.25'
GOSUMDB='sum.golang.org'
GOTELEMETRY='on'
GOTELEMETRYDIR='/home/ext_zdilap/.config/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='local'
GOTOOLDIR='/usr/lib/go-1.25/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.25.3'
GOWORK=''
PKG_CONFIG='pkg-config'What did you do?
When ptr argument to runtime.AddCleanup is a struct with a single numeric field, the cleanup function is never called.
Cleanup is called if the field is of type string, or if a struct has two numeric fields.
Runnable example:
https://go.dev/play/p/71v_vLeVMUo
This may be problematic in practice if someone, for some odd reason, writes a file-descriptor-wrapper structure and relies on GC to close the file descriptor.
For example, this program starts printing Read error: too many open files on my machine after reaching N open files, where N is the configured ulimit -n:
package main
import (
"fmt"
"runtime"
"syscall"
)
type File struct {
fd int
}
func main() {
for {
OpenAndProcessFile("/dev/zero")
}
}
func OpenAndProcessFile(path string) {
fd, err := syscall.Open(path, syscall.O_RDONLY, 0)
if err != nil {
fmt.Printf("Read error: %v\n", err)
return
}
ProcessFile(fd)
}
func ProcessFile(fd int) {
var called bool
func() {
f := new(File)
f.fd = fd
runtime.AddCleanup(f, func(fd int) {
syscall.Close(fd)
called = true
}, fd)
// Do something with f...
b := make([]byte, 10)
n, err := syscall.Read(f.fd, b)
if err != nil {
fmt.Printf("Read error: %v\n", err)
return
}
fmt.Printf("Read %d bytes: %v\n", n, b[:n])
}()
// Force garbage collection multiple times to ensure cleanup is called
for range 10 {
runtime.GC()
}
fmt.Printf("Cleanup called for File: %v\n", called)
}What did you see happen?
Cleanup is not called for structures with a single numerical field.
What did you expect to see?
Cleanup is called for all structures, regardless of their internals.
see #76007 (comment)