Skip to content

Commit ae52e0c

Browse files
committed
config: switch PidsLimit to *int64
Previously we would treat a value of "0" as meaning "no-op", which lead to quite a bit of confusion. The runtime-spec has been updated to make the "pids.limit" value a pointer, and explicitly state that: * Values >= 0 should be treated as normal values; and * < 0 (usually -1) indicates "max". In practice this means we should switch PidsLimit to an *int64. Luckily, this is actually backwards-compatible with our previous JSON -- an old value of "0" would be omitted from the output, which will now be parsed as "nil". The handling by our cgroup code would be identical but the latter now correctly reflects the guidance by the runtime-spec. Signed-off-by: Aleksa Sarai <[email protected]>
1 parent 6a793b6 commit ae52e0c

File tree

7 files changed

+113
-27
lines changed

7 files changed

+113
-27
lines changed

config_linux.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,8 @@ type Resources struct {
9090
// Cgroup's SCHED_IDLE value.
9191
CPUIdle *int64 `json:"cpu_idle,omitempty"`
9292

93-
// Process limit; set <= `0' to disable limit.
94-
PidsLimit int64 `json:"pids_limit,omitempty"`
93+
// Process limit; set < `0' to disable limit. `nil` means "keep current limit".
94+
PidsLimit *int64 `json:"pids_limit,omitempty"`
9595

9696
// Specifies per cgroup weight, range is from 10 to 1000.
9797
BlkioWeight uint16 `json:"blkio_weight,omitempty"`

devices/systemd_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import (
1313
"github.com/opencontainers/cgroups/systemd"
1414
)
1515

16+
func toPtr[T any](v T) *T { return &v }
17+
1618
// TestPodSkipDevicesUpdate checks that updating a pod having SkipDevices: true
1719
// does not result in spurious "permission denied" errors in a container
1820
// running under the pod. The test is somewhat similar in nature to the
@@ -32,7 +34,7 @@ func TestPodSkipDevicesUpdate(t *testing.T) {
3234
Parent: "system.slice",
3335
Name: podName,
3436
Resources: &cgroups.Resources{
35-
PidsLimit: 42,
37+
PidsLimit: toPtr[int64](42),
3638
Memory: 32 * 1024 * 1024,
3739
SkipDevices: true,
3840
},
@@ -96,7 +98,7 @@ func TestPodSkipDevicesUpdate(t *testing.T) {
9698

9799
// Now update the pod a few times.
98100
for range 42 {
99-
podConfig.Resources.PidsLimit++
101+
(*podConfig.Resources.PidsLimit)++
100102
podConfig.Resources.Memory += 1024 * 1024
101103
if err := pm.Set(podConfig.Resources); err != nil {
102104
t.Fatal(err)

fs/pids.go

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,24 @@ func (s *PidsGroup) Apply(path string, _ *cgroups.Resources, pid int) error {
1919
}
2020

2121
func (s *PidsGroup) Set(path string, r *cgroups.Resources) error {
22-
if r.PidsLimit != 0 {
23-
// "max" is the fallback value.
24-
limit := "max"
25-
26-
if r.PidsLimit > 0 {
27-
limit = strconv.FormatInt(r.PidsLimit, 10)
28-
}
29-
30-
if err := cgroups.WriteFile(path, "pids.max", limit); err != nil {
31-
return err
32-
}
22+
if r.PidsLimit == nil {
23+
return nil
3324
}
3425

26+
// "max" is the fallback value.
27+
val := "max"
28+
if limit := *r.PidsLimit; limit > 0 {
29+
val = strconv.FormatInt(limit, 10)
30+
} else if limit == 0 {
31+
// systemd doesn't support setting pids.max to "0", so when setting
32+
// TasksMax we need to remap it to "1". We do the same thing here to
33+
// avoid flip-flop behaviour between the fs and systemd drivers. In
34+
// practice, the pids cgroup behaviour is basically identical.
35+
val = "1"
36+
}
37+
if err := cgroups.WriteFile(path, "pids.max", val); err != nil {
38+
return err
39+
}
3540
return nil
3641
}
3742

fs/pids_test.go

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ const (
1313
maxLimited = 1024
1414
)
1515

16+
func toPtr[T any](v T) *T { return &v }
17+
1618
func TestPidsSetMax(t *testing.T) {
1719
path := tempDir(t, "pids")
1820

@@ -21,7 +23,7 @@ func TestPidsSetMax(t *testing.T) {
2123
})
2224

2325
r := &cgroups.Resources{
24-
PidsLimit: maxLimited,
26+
PidsLimit: toPtr[int64](maxLimited),
2527
}
2628
pids := &PidsGroup{}
2729
if err := pids.Set(path, r); err != nil {
@@ -37,6 +39,55 @@ func TestPidsSetMax(t *testing.T) {
3739
}
3840
}
3941

42+
func TestPidsSetZero(t *testing.T) {
43+
path := tempDir(t, "pids")
44+
45+
writeFileContents(t, path, map[string]string{
46+
"pids.max": "max",
47+
})
48+
49+
r := &cgroups.Resources{
50+
PidsLimit: toPtr[int64](0),
51+
}
52+
pids := &PidsGroup{}
53+
if err := pids.Set(path, r); err != nil {
54+
t.Fatal(err)
55+
}
56+
57+
value, err := fscommon.GetCgroupParamUint(path, "pids.max")
58+
if err != nil {
59+
t.Fatal(err)
60+
}
61+
// See comment in (*PidsGroup).Set for why we set to 1 here.
62+
if value != 1 {
63+
t.Fatalf("Expected 1, got %d for setting pids.max = 0", value)
64+
}
65+
}
66+
67+
func TestPidsUnset(t *testing.T) {
68+
path := tempDir(t, "pids")
69+
70+
writeFileContents(t, path, map[string]string{
71+
"pids.max": "12345",
72+
})
73+
74+
r := &cgroups.Resources{
75+
PidsLimit: nil,
76+
}
77+
pids := &PidsGroup{}
78+
if err := pids.Set(path, r); err != nil {
79+
t.Fatal(err)
80+
}
81+
82+
value, err := fscommon.GetCgroupParamUint(path, "pids.max")
83+
if err != nil {
84+
t.Fatal(err)
85+
}
86+
if value != 12345 {
87+
t.Fatalf("Expected 12345, got %d for not setting pids.max", value)
88+
}
89+
}
90+
4091
func TestPidsSetUnlimited(t *testing.T) {
4192
path := tempDir(t, "pids")
4293

@@ -45,7 +96,7 @@ func TestPidsSetUnlimited(t *testing.T) {
4596
})
4697

4798
r := &cgroups.Resources{
48-
PidsLimit: maxUnlimited,
99+
PidsLimit: toPtr[int64](maxUnlimited),
49100
}
50101
pids := &PidsGroup{}
51102
if err := pids.Set(path, r); err != nil {

fs2/pids.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"errors"
55
"math"
66
"os"
7+
"strconv"
78
"strings"
89

910
"golang.org/x/sys/unix"
@@ -13,19 +14,26 @@ import (
1314
)
1415

1516
func isPidsSet(r *cgroups.Resources) bool {
16-
return r.PidsLimit != 0
17+
return r.PidsLimit != nil
1718
}
1819

1920
func setPids(dirPath string, r *cgroups.Resources) error {
2021
if !isPidsSet(r) {
2122
return nil
2223
}
23-
if val := numToStr(r.PidsLimit); val != "" {
24-
if err := cgroups.WriteFile(dirPath, "pids.max", val); err != nil {
25-
return err
26-
}
24+
val := "max"
25+
if limit := *r.PidsLimit; limit > 0 {
26+
val = strconv.FormatInt(limit, 10)
27+
} else if limit == 0 {
28+
// systemd doesn't support setting pids.max to "0", so when setting
29+
// TasksMax we need to remap it to "1". We do the same thing here to
30+
// avoid flip-flop behaviour between the fs and systemd drivers. In
31+
// practice, the pids cgroup behaviour is basically identical.
32+
val = "1"
33+
}
34+
if err := cgroups.WriteFile(dirPath, "pids.max", val); err != nil {
35+
return err
2736
}
28-
2937
return nil
3038
}
3139

systemd/v1.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package systemd
22

33
import (
44
"errors"
5+
"math"
56
"os"
67
"path/filepath"
78
"strings"
@@ -97,9 +98,17 @@ func genV1ResourcesProperties(r *cgroups.Resources, cm *dbusConnManager) ([]syst
9798
newProp("BlockIOWeight", uint64(r.BlkioWeight)))
9899
}
99100

100-
if r.PidsLimit > 0 || r.PidsLimit == -1 {
101+
if r.PidsLimit != nil {
102+
var tasksMax uint64
103+
if limit := *r.PidsLimit; limit < 0 {
104+
tasksMax = math.MaxUint64 // "infinity"
105+
} else if limit == 0 {
106+
tasksMax = 1 // systemd does not accept "0" for TasksMax
107+
} else {
108+
tasksMax = uint64(limit)
109+
}
101110
properties = append(properties,
102-
newProp("TasksMax", uint64(r.PidsLimit)))
111+
newProp("TasksMax", tasksMax))
103112
}
104113

105114
err = addCpuset(cm, &properties, r.CpusetCpus, r.CpusetMems)

systemd/v2.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,9 @@ func unifiedResToSystemdProps(cm *dbusConnManager, res map[string]string) (props
176176
return nil, fmt.Errorf("unified resource %q value conversion error: %w", k, err)
177177
}
178178
}
179+
if num == 0 {
180+
num = 1 // systemd does not accept "0" for TasksMax
181+
}
179182
props = append(props,
180183
newProp("TasksMax", num))
181184

@@ -256,9 +259,17 @@ func genV2ResourcesProperties(dirPath string, r *cgroups.Resources, cm *dbusConn
256259

257260
addCPUQuota(cm, &properties, &r.CpuQuota, r.CpuPeriod)
258261

259-
if r.PidsLimit > 0 || r.PidsLimit == -1 {
262+
if r.PidsLimit != nil {
263+
var tasksMax uint64
264+
if limit := *r.PidsLimit; limit < 0 {
265+
tasksMax = math.MaxUint64 // "infinity"
266+
} else if limit == 0 {
267+
tasksMax = 1 // systemd does not accept "0" for TasksMax
268+
} else {
269+
tasksMax = uint64(limit)
270+
}
260271
properties = append(properties,
261-
newProp("TasksMax", uint64(r.PidsLimit)))
272+
newProp("TasksMax", tasksMax))
262273
}
263274

264275
err = addCpuset(cm, &properties, r.CpusetCpus, r.CpusetMems)

0 commit comments

Comments
 (0)