-
Notifications
You must be signed in to change notification settings - Fork 18.9k
Description
Go 1.14 release notes (https://golang.org/doc/go1.14#runtime) say
programs that use packages like
syscallorgolang.org/x/sys/unixwill see more slow system calls fail withEINTRerrors. Those programs will have to handle those errors in some way, most likely looping to try the system call again. For more information about this seeman 7 signalfor Linux systems or similar documentation for other systems.
This gives me an impression that unless I'm using syscall or golang.org/x/sys/unix directly, my code is fine.
From the signal(7) man page on Linux:
If a blocked call to one of the following interfaces is interrupted by a signal handler, then the call is automatically restarted after the signal handler returns if the SA_RESTART flag was used;
<...>
read(2), readv(2), write(2), writev(2), and ioctl(2) calls <...>
Assuming that golang runtime always sets signal handlers with SA_RESTART, taken all the above into account, it seems right to conclude that using a high-level write function such as ioutils.WriteFile or os.Write should be fine.
Turns out it's not.
Originally reported in opencontainers/runc#2258
What version of Go are you using (go version)?
$ go1.14 version go version go1.14 linux/amd64
Does this issue reproduce with the latest release?
Yes
What operating system and processor architecture are you using (go env)?
go env Output
$ go1.14 env GO111MODULE="" GOARCH="amd64" GOBIN="" GOCACHE="/home/kir/.cache/go-build" GOENV="/home/kir/.config/go/env" GOEXE="" GOFLAGS="" GOHOSTARCH="amd64" GOHOSTOS="linux" GOINSECURE="" GONOPROXY="" GONOSUMDB="" GOOS="linux" GOPATH="/home/kir/go" GOPRIVATE="" GOPROXY="https://proxy.golang.org,direct" GOROOT="/home/kir/sdk/go1.14" GOSUMDB="sum.golang.org" GOTMPDIR="" GOTOOLDIR="/home/kir/sdk/go1.14/pkg/tool/linux_amd64" GCCGO="gccgo" AR="ar" CC="gcc" CXX="g++" CGO_ENABLED="1" GOMOD="/home/kir/go/src/github.com/opencontainers/runc/go.mod" CGO_CFLAGS="-g -O2" CGO_CPPFLAGS="" CGO_CXXFLAGS="-g -O2" CGO_FFLAGS="-g -O2" CGO_LDFLAGS="-g -O2" PKG_CONFIG="pkg-config" GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build157351814=/tmp/go-build -gno-record-gcc-switches"
What did you do?
The code below should be compiled (go test -c) and run as root on a modern Linux system.
package fscommon
import (
"io/ioutil"
"os"
"path"
"strconv"
"testing"
)
const iter = 1000000
func TestWriteHandlesEINTR(t *testing.T) {
memoryCgroupMount := "/sys/fs/cgroup/memory"
cgroupPath, err := ioutil.TempDir(memoryCgroupMount, "test-")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(cgroupPath)
filename := path.Join(cgroupPath, "memory.limit_in_bytes")
for i := 0; i < iter; i++ {
limit := 1024*1024 + i
f, err := os.OpenFile(filename, os.O_WRONLY, 0644)
if err != nil {
t.Fatal(err)
}
if n, err := f.WriteString(strconv.Itoa(limit)); err != nil {
t.Fatalf("Failed to write %d on attempt %d (wrote %d bytes): %s", limit, i, n, err)
}
f.Close()
}
}
func TestWriteFileHandlesEINTR(t *testing.T) {
memoryCgroupMount := "/sys/fs/cgroup/memory"
cgroupPath, err := ioutil.TempDir(memoryCgroupMount, "test-")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(cgroupPath)
file := path.Join(cgroupPath, "memory.limit_in_bytes")
for i := 0; i < iter; i++ {
limit := 1024*1024 + i
if err := ioutil.WriteFile(file, []byte(strconv.Itoa(limit)), 0644); err != nil {
t.Fatalf("Failed to write %d on attempt %d: %s", limit, i, err)
}
}
}What did you expect to see?
PASS
(the test passes with go 1.13.6 and probably all earlier versions)
What did you see instead?
[kir@kir-rhat fscommon]$ go1.14 test -c fscommon_test.go
[kir@kir-rhat fscommon]$ sudo ./fscommon.test
[sudo] password for kir:
--- FAIL: TestWriteHandlesEINTR (0.10s)
fscommon_test.go:30: Failed to write 1072354 on attempt 23778 (wrote 0 bytes): write /sys/fs/cgroup/memory/test-298000507/memory.limit_in_bytes: interrupted system call
--- FAIL: TestWriteFileHandlesEINTR (0.19s)
fscommon_test.go:48: Failed to write 1094511 on attempt 45935: write /sys/fs/cgroup/memory/test-239836062/memory.limit_in_bytes: interrupted system call
FAIL