Skip to content

Commit 2f41057

Browse files
authored
Merge pull request #48 from cyphar/pids-limit-0
config: switch PidsLimit to *int64
2 parents 6a793b6 + 7c34f09 commit 2f41057

File tree

8 files changed

+150
-27
lines changed

8 files changed

+150
-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/systemd_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,3 +236,40 @@ func TestAddCPUQuota(t *testing.T) {
236236
})
237237
}
238238
}
239+
240+
func TestTasksMax(t *testing.T) {
241+
if !IsRunningSystemd() {
242+
t.Skip("Test requires systemd.")
243+
}
244+
if os.Geteuid() != 0 {
245+
t.Skip("Test requires root.")
246+
}
247+
248+
podConfig := &cgroups.Cgroup{
249+
Parent: "system.slice",
250+
Name: "system-runc_test_tasksmax.slice",
251+
Resources: &cgroups.Resources{},
252+
}
253+
// Create "pods" cgroup (a systemd slice to hold containers).
254+
pm := newManager(t, podConfig)
255+
if err := pm.Apply(-1); err != nil {
256+
t.Fatal(err)
257+
}
258+
259+
res := &cgroups.Resources{PidsLimit: nil}
260+
if err := pm.Set(res); err != nil {
261+
t.Fatalf("failed to set PidsLimit=nil: %v", err)
262+
}
263+
264+
for _, limit := range []int64{100, 0, 42, -1, 100, -99} {
265+
res.PidsLimit = &limit
266+
if err := pm.Set(res); err != nil {
267+
t.Fatalf("failed to set PidsLimit=%d: %v", limit, err)
268+
}
269+
}
270+
271+
res.PidsLimit = nil
272+
if err := pm.Set(res); err != nil {
273+
t.Fatalf("failed to set PidsLimit=nil: %v", err)
274+
}
275+
}

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)