Retry writing to cgroup files on EINTR error

Golang 1.14 introduces asynchronous preemption which results into
applications getting frequent EINTR (syscall interrupted) errors when
invoking slow syscalls, e.g. when writing to cgroup files.

As writing to cgroups is idempotent, it is safe to retry writing to the
file whenever the write syscall is interrupted.

Signed-off-by: Mario Nitchev <marionitchev@gmail.com>
This commit is contained in:
Danail Branekov 2020-03-18 13:00:05 +02:00 committed by Mario Nitchev
parent 939cd0b734
commit f34eb2c003
2 changed files with 60 additions and 1 deletions

View File

@ -4,9 +4,12 @@ package fscommon
import (
"io/ioutil"
"os"
"syscall"
securejoin "github.com/cyphar/filepath-securejoin"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
func WriteFile(dir, file, data string) error {
@ -17,7 +20,7 @@ func WriteFile(dir, file, data string) error {
if err != nil {
return err
}
if err := ioutil.WriteFile(path, []byte(data), 0700); err != nil {
if err := retryingWriteFile(path, []byte(data), 0700); err != nil {
return errors.Wrapf(err, "failed to write %q to %q", data, path)
}
return nil
@ -34,3 +37,24 @@ func ReadFile(dir, file string) (string, error) {
data, err := ioutil.ReadFile(path)
return string(data), err
}
func retryingWriteFile(filename string, data []byte, perm os.FileMode) error {
for {
err := ioutil.WriteFile(filename, data, perm)
if isInterruptedWriteFile(err) {
logrus.Infof("interrupted while writing %s to %s", string(data), filename)
continue
}
return err
}
}
func isInterruptedWriteFile(err error) bool {
if patherr, ok := err.(*os.PathError); ok {
errno, ok2 := patherr.Err.(syscall.Errno)
if ok2 && errno == syscall.EINTR {
return true
}
}
return false
}

View File

@ -0,0 +1,35 @@
// +build linux
package fscommon
import (
"fmt"
"os"
"path/filepath"
"strconv"
"testing"
"time"
"github.com/opencontainers/runc/libcontainer/cgroups"
)
func TestWriteCgroupFileHandlesInterrupt(t *testing.T) {
memoryCgroupMount, err := cgroups.FindCgroupMountpoint("", "memory")
if err != nil {
t.Fatal(err)
}
cgroupName := fmt.Sprintf("test-eint-%d", time.Now().Nanosecond())
cgroupPath := filepath.Join(memoryCgroupMount, cgroupName)
if err := os.MkdirAll(cgroupPath, 0755); err != nil {
t.Fatal(err)
}
defer os.RemoveAll(cgroupPath)
for i := 0; i < 100000; i++ {
limit := 1024*1024 + i
if err := WriteFile(cgroupPath, "memory.limit_in_bytes", strconv.Itoa(limit)); err != nil {
t.Fatalf("Failed to write %d on attempt %d: %+v", limit, i, err)
}
}
}