Skip to content

Commit 7719036

Browse files
committed
libct/cg: write unified resources line by line
It has been pointed out that some controllers can not accept multiple lines of output at once. In particular, io.max can only set one device at a time. Practically, the only multi-line resource values we can get come from unified.* -- let's write those line by line. Add a test case. Reported-by: Tao Shen <[email protected]> Signed-off-by: Kir Kolyshkin <[email protected]>
1 parent 30b7b63 commit 7719036

File tree

4 files changed

+89
-1
lines changed

4 files changed

+89
-1
lines changed

libcontainer/cgroups/file.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,40 @@ func WriteFile(dir, file, data string) error {
5757
return nil
5858
}
5959

60+
// WriteFileByLine is the same as WriteFile, except if data contains newlines,
61+
// it is written line by line.
62+
func WriteFileByLine(dir, file, data string) error {
63+
i := strings.Index(data, "\n")
64+
if i == -1 {
65+
return WriteFile(dir, file, data)
66+
}
67+
68+
fd, err := OpenFile(dir, file, unix.O_WRONLY)
69+
if err != nil {
70+
return err
71+
}
72+
defer fd.Close()
73+
start := 0
74+
for {
75+
var line string
76+
if i == -1 {
77+
line = data[start:]
78+
} else {
79+
line = data[start : start+i+1]
80+
}
81+
_, err := fd.WriteString(line)
82+
if err != nil {
83+
return fmt.Errorf("failed to write %q: %w", line, err)
84+
}
85+
if i == -1 {
86+
break
87+
}
88+
start += i + 1
89+
i = strings.Index(data[start:], "\n")
90+
}
91+
return nil
92+
}
93+
6094
const (
6195
cgroupfsDir = "/sys/fs/cgroup"
6296
cgroupfsPrefix = cgroupfsDir + "/"

libcontainer/cgroups/file_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,25 @@ func TestOpenat2(t *testing.T) {
7373
fd.Close()
7474
}
7575
}
76+
77+
func BenchmarkWriteFile(b *testing.B) {
78+
TestMode = true
79+
defer func() { TestMode = false }()
80+
81+
dir := b.TempDir()
82+
tc := []string{
83+
"one",
84+
"one\ntwo\nthree",
85+
"10:200 foo=bar boo=far\n300:1200 something=other\ndefault 45000\n",
86+
"\n\n\n\n\n\n\n\n",
87+
}
88+
89+
b.ResetTimer()
90+
for i := 0; i < b.N; i++ {
91+
for _, val := range tc {
92+
if err := WriteFileByLine(dir, "file", val); err != nil {
93+
b.Fatal(err)
94+
}
95+
}
96+
}
97+
}

libcontainer/cgroups/fs2/fs2.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ func (m *Manager) setUnified(res map[string]string) error {
238238
if strings.Contains(k, "/") {
239239
return fmt.Errorf("unified resource %q must be a file name (no slashes)", k)
240240
}
241-
if err := cgroups.WriteFile(m.dirPath, k, v); err != nil {
241+
if err := cgroups.WriteFileByLine(m.dirPath, k, v); err != nil {
242242
// Check for both EPERM and ENOENT since O_CREAT is used by WriteFile.
243243
if errors.Is(err, os.ErrPermission) || errors.Is(err, os.ErrNotExist) {
244244
// Check if a controller is available,

tests/integration/cgroups.bats

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,38 @@ function setup() {
187187
[[ "$weights" == *"$major:$minor 444"* ]]
188188
}
189189

190+
@test "runc run (per-device multiple iops via unified)" {
191+
requires root cgroups_v2
192+
193+
dd if=/dev/zero of=backing1.img bs=4096 count=1
194+
dev1=$(losetup --find --show backing1.img) || skip "unable to create a loop device"
195+
196+
# Second device.
197+
dd if=/dev/zero of=backing2.img bs=4096 count=1
198+
dev2=$(losetup --find --show backing2.img) || skip "unable to create a loop device"
199+
200+
set_cgroups_path
201+
202+
IFS=$' \t:' read -r major1 minor1 <<<"$(lsblk -nd -o MAJ:MIN "$dev1")"
203+
IFS=$' \t:' read -r major2 minor2 <<<"$(lsblk -nd -o MAJ:MIN "$dev2")"
204+
update_config ' .linux.devices += [
205+
{path: "'"$dev1"'", type: "b", major: '"$major1"', minor: '"$minor1"'},
206+
{path: "'"$dev2"'", type: "b", major: '"$major2"', minor: '"$minor2"'}
207+
]
208+
| .linux.resources.unified |=
209+
{"io.max": "'"$major1"':'"$minor1"' riops=333 wiops=444\n'"$major2"':'"$minor2"' riops=555 wiops=666\n"}'
210+
runc run -d --console-socket "$CONSOLE_SOCKET" test_dev_weight
211+
[ "$status" -eq 0 ]
212+
213+
# The loop devices are no longer needed.
214+
losetup -d "$dev1"
215+
losetup -d "$dev2"
216+
217+
weights=$(get_cgroup_value "io.max")
218+
grep "^$major1:$minor1 .* riops=333 wiops=444$" <<<"$weights"
219+
grep "^$major2:$minor2 .* riops=555 wiops=666$" <<<"$weights"
220+
}
221+
190222
@test "runc run (cpu.idle)" {
191223
requires cgroups_cpu_idle
192224
[ $EUID -ne 0 ] && requires rootless_cgroup

0 commit comments

Comments
 (0)