Skip to content

Commit 1b2adcf

Browse files
committed
libct/cg/v1: workaround CPU quota period set failure
As reported in issue 3084, sometimes setting CPU quota period fails when a new period is lower and a parent cgroup has CPU quota limit set. This happens as in cgroup v1 the quota and the period can not be set together (this is fixed in v2), and since the period is being set first, new_limit = old_quota/new_period may be higher than the parent cgroup limit. The fix is to retry setting the period after the quota, to cover all possible scenarios. Add a test case to cover a regression caused by an earlier version of this patch (ignoring a failure of setting invalid period when quota is not set). Signed-off-by: Kir Kolyshkin <[email protected]>
1 parent 654f331 commit 1b2adcf

File tree

2 files changed

+31
-2
lines changed

2 files changed

+31
-2
lines changed

libcontainer/cgroups/fs/cpu.go

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ package fs
44

55
import (
66
"bufio"
7+
"errors"
78
"fmt"
89
"os"
910
"strconv"
1011

1112
"github.com/opencontainers/runc/libcontainer/cgroups"
1213
"github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
1314
"github.com/opencontainers/runc/libcontainer/configs"
15+
"golang.org/x/sys/unix"
1416
)
1517

1618
type CpuGroup struct{}
@@ -71,15 +73,33 @@ func (s *CpuGroup) Set(path string, r *configs.Resources) error {
7173
return fmt.Errorf("the minimum allowed cpu-shares is %d", sharesRead)
7274
}
7375
}
76+
77+
var period string
7478
if r.CpuPeriod != 0 {
75-
if err := cgroups.WriteFile(path, "cpu.cfs_period_us", strconv.FormatUint(r.CpuPeriod, 10)); err != nil {
76-
return err
79+
period = strconv.FormatUint(r.CpuPeriod, 10)
80+
if err := cgroups.WriteFile(path, "cpu.cfs_period_us", period); err != nil {
81+
// Sometimes when the period to be set is smaller
82+
// than the current one, it is rejected by the kernel
83+
// (EINVAL) as old_quota/new_period exceeds the parent
84+
// cgroup quota limit. If this happens and the quota is
85+
// going to be set, ignore the error for now and retry
86+
// after setting the quota.
87+
if !errors.Is(err, unix.EINVAL) || r.CpuQuota == 0 {
88+
return err
89+
}
90+
} else {
91+
period = ""
7792
}
7893
}
7994
if r.CpuQuota != 0 {
8095
if err := cgroups.WriteFile(path, "cpu.cfs_quota_us", strconv.FormatInt(r.CpuQuota, 10)); err != nil {
8196
return err
8297
}
98+
if period != "" {
99+
if err := cgroups.WriteFile(path, "cpu.cfs_period_us", period); err != nil {
100+
return err
101+
}
102+
}
83103
}
84104
return s.SetRtSched(path, r)
85105
}

tests/integration/update.bats

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,15 @@ EOF
345345
check_cpu_quota -1 1000000 "infinity"
346346
}
347347

348+
@test "set cpu period with no quota (invalid period)" {
349+
[[ "$ROOTLESS" -ne 0 ]] && requires rootless_cgroup
350+
351+
update_config '.linux.resources.cpu |= { "period": 100 }'
352+
353+
runc run -d --console-socket "$CONSOLE_SOCKET" test_update
354+
[ "$status" -eq 1 ]
355+
}
356+
348357
@test "set cpu quota with no period" {
349358
[[ "$ROOTLESS" -ne 0 ]] && requires rootless_cgroup
350359

0 commit comments

Comments
 (0)