Skip to content

os/exec: resource leak on exec failure #69284

Closed
@rustyx

Description

@rustyx

Go version

go version go1.23.0 linux/amd64

Output of go env in your module/workspace:

GO111MODULE=''
GOARCH='amd64'
GOBIN=''
GOCACHE='/home/user/.cache/go-build'
GOENV='/home/user/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMODCACHE='/home/user/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/home/user/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/local/go'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/usr/local/go/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.23.0'
GODEBUG=''
GOTELEMETRY='local'
GOTELEMETRYDIR='/home/user/.config/go/telemetry'
GCCGO='gccgo'
GOAMD64='v1'
AR='ar'
CC='gcc'
CXX='g++'
CGO_ENABLED='1'
GOMOD='/dev/null'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build2691713470=/tmp/go-build -gno-record-gcc-switches'

What did you do?

It seems that when process.Start fails, there are some files left open, which after a while leads to a "too many open files" error.

Here's a minimal repro:

package main

import (
	"context"
	"errors"
	"os/exec"
	"syscall"
	"testing"
)

func TestExecResources(t *testing.T) {
	ctx := context.Background()
	var oldRLimit syscall.Rlimit
	err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &oldRLimit)
	if err != nil {
		t.Fatal(err)
	}
	newRLimit := oldRLimit
	newRLimit.Cur = 20
	err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &newRLimit)
	if err != nil {
		t.Fatal(err)
	}
	defer func() {
		syscall.Setrlimit(syscall.RLIMIT_NOFILE, &oldRLimit)
	}()

	for i := 0; i < 22; i++ {
		ctx, cancel := context.WithCancel(ctx)
		process := exec.CommandContext(ctx, "/bin/nonexistent")
		err = process.Start()
		cancel()
		process.Wait() // not really needed, just to demonstrate that even calling Wait doesn't help
		var se syscall.Errno
		if !errors.As(err, &se) || se != syscall.ENOENT {
			t.Fatal(err)
		}
	}
}

What did you see happen?

The test fails with:

--- FAIL: TestExecResources (0.00s)
    process_unix_test.go:35: fork/exec /bin/nonexistent: too many open files

What did you expect to see?

I expected the test to pass.

Note that the issue occurs also on other exec failures, such as permission denied, invalid ELF format, etc.

Metadata

Metadata

Assignees

No one assigned

    Labels

    FixPendingIssues that have a fix which has not yet been reviewed or submitted.NeedsFixThe path to resolution is known, but the work has not been done.OS-Linux

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions