Skip to content

Commit 0080fa4

Browse files
Tycho Andersenjadams41
andcommitted
v2: set memory.oom.group => OOMPolicy in systemd
We are interested in using memory.oom.cgroup, but need it to be set systemd because of [1], so let's set it. There are a few caveats, in no particular order: A. systemd does not allow OOMPolicy to be set on units that have already started, so we must do this in Apply() instead of Set(). B. As the comment suggests, OOMPolicy has three states (continue, stop, kill), where kill maps to memory.oom.group=1, and continue maps to =0. However, the bit about `runc update` doesn't quite make sense: the values will only ever be expressed in terms of memory.oom.group, so we only need to map the continue and kill values, which have direct mappings. Note that `runc update` here doesn't make sense anyway: because of (A), we cannot update these values. Perhaps we should reject these updates since systemd will? (Or maybe we try to update and just error out, in the event that systemd eventually allows this? The kernel allows updating it, the reason the systemd semantics have diverged is unclear.) C. systemd only gained support for setting OOMPolicy on scopes in versions >= 253; versions before this will fail. So, let's add a bit allowing the setup of OOMPolicy to Apply(), and ignore it in Set() -> genV2ResourcesProperties() -> unifiedResToSystemdProps(). [1]: This arguably is more important than the debug-level warning would suggest: if someone does the equivalent of a `systemctl daemon-reload`, systemd will reset our manually-via-cgroupfs set value to 0, because we did not explicitly set it in the service / scope definition, meaning that individual tasks will not actually oom the whole cgroup when they oom. Co-authored-by: Ethan Adams <[email protected]> Signed-off-by: Tycho Andersen <[email protected]>
1 parent 6a793b6 commit 0080fa4

File tree

2 files changed

+123
-7
lines changed

2 files changed

+123
-7
lines changed

systemd/systemd_test.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package systemd
22

33
import (
4+
"context"
45
"os"
56
"reflect"
7+
"strconv"
68
"testing"
79

810
systemdDbus "github.com/coreos/go-systemd/v22/dbus"
@@ -157,6 +159,12 @@ func TestUnifiedResToSystemdProps(t *testing.T) {
157159
newProp("CPUWeight", uint64(1000)),
158160
},
159161
},
162+
{
163+
name: "memory.oom.group handled by Apply method",
164+
res: map[string]string{
165+
"memory.oom.group": "1",
166+
},
167+
},
160168
}
161169

162170
for _, tc := range testCases {
@@ -236,3 +244,98 @@ func TestAddCPUQuota(t *testing.T) {
236244
})
237245
}
238246
}
247+
248+
func TestOOMPolicyApply(t *testing.T) {
249+
if !IsRunningSystemd() {
250+
t.Skip("Test requires systemd.")
251+
}
252+
if !cgroups.IsCgroup2UnifiedMode() {
253+
t.Skip("cgroup v2 is required")
254+
}
255+
if os.Geteuid() != 0 {
256+
t.Skip("Test requires root.")
257+
}
258+
259+
testCases := []struct {
260+
name string
261+
oomGroupValue string
262+
expectedPolicy string
263+
expectError bool
264+
}{
265+
{
266+
name: "memory.oom.group=0 sets OOMPolicy=continue",
267+
oomGroupValue: "0",
268+
expectedPolicy: "continue",
269+
expectError: false,
270+
},
271+
{
272+
name: "memory.oom.group=1 sets OOMPolicy=kill",
273+
oomGroupValue: "1",
274+
expectedPolicy: "kill",
275+
expectError: false,
276+
},
277+
{
278+
name: "invalid memory.oom.group value",
279+
oomGroupValue: "invalid",
280+
expectError: true,
281+
},
282+
}
283+
284+
for _, tc := range testCases {
285+
t.Run(tc.name, func(t *testing.T) {
286+
config := &cgroups.Cgroup{
287+
Name: "test-oom-policy-" + strconv.FormatInt(int64(os.Getpid()), 10),
288+
Resources: &cgroups.Resources{
289+
Unified: map[string]string{
290+
"memory.oom.group": tc.oomGroupValue,
291+
},
292+
},
293+
}
294+
295+
manager, err := NewUnifiedManager(config, "")
296+
if err != nil {
297+
t.Fatalf("Failed to create manager: %v", err)
298+
}
299+
defer func() {
300+
_ = manager.Destroy()
301+
}()
302+
303+
err = manager.Apply(-1)
304+
if tc.expectError {
305+
if err == nil {
306+
t.Fatal("Expected error but got none")
307+
}
308+
return
309+
}
310+
if err != nil {
311+
t.Fatalf("Unexpected error: %v", err)
312+
}
313+
314+
unitName := getUnitName(config)
315+
conn, err := systemdDbus.NewSystemdConnectionContext(context.Background())
316+
if err != nil {
317+
t.Fatalf("Failed to connect to systemd: %v", err)
318+
}
319+
defer conn.Close()
320+
321+
properties, err := conn.GetUnitPropertiesContext(context.Background(), unitName)
322+
if err != nil {
323+
t.Fatalf("Failed to get unit properties: %v", err)
324+
}
325+
326+
oomPolicyValue, exists := properties["OOMPolicy"]
327+
if !exists {
328+
t.Fatal("OOMPolicy property not found")
329+
}
330+
331+
oomPolicyStr, ok := oomPolicyValue.(string)
332+
if !ok {
333+
t.Fatalf("OOMPolicy value is not a string: %T", oomPolicyValue)
334+
}
335+
336+
if oomPolicyStr != tc.expectedPolicy {
337+
t.Errorf("Expected OOMPolicy=%s, got %s", tc.expectedPolicy, oomPolicyStr)
338+
}
339+
})
340+
}
341+
}

systemd/v2.go

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -180,13 +180,8 @@ func unifiedResToSystemdProps(cm *dbusConnManager, res map[string]string) (props
180180
newProp("TasksMax", num))
181181

182182
case "memory.oom.group":
183-
// Setting this to 1 is roughly equivalent to OOMPolicy=kill
184-
// (as per systemd.service(5) and
185-
// https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html),
186-
// but it's not clear what to do if it is unset or set
187-
// to 0 in runc update, as there are two other possible
188-
// values for OOMPolicy (continue/stop).
189-
fallthrough
183+
// This was set before the unit started, so no need to
184+
// warn about it here.
190185

191186
default:
192187
// Ignore the unknown resource here -- will still be
@@ -327,6 +322,24 @@ func (m *UnifiedManager) Apply(pid int) error {
327322

328323
properties = append(properties, c.SystemdProps...)
329324

325+
if c.Resources != nil && c.Resources.Unified != nil {
326+
if v, ok := c.Resources.Unified["memory.oom.group"]; ok {
327+
value, err := strconv.ParseUint(v, 10, 64)
328+
if err != nil {
329+
return fmt.Errorf("unified resource %q value conversion error: %w", "memory.oom.group", err)
330+
}
331+
332+
switch value {
333+
case 0:
334+
properties = append(properties, newProp("OOMPolicy", "continue"))
335+
case 1:
336+
properties = append(properties, newProp("OOMPolicy", "kill"))
337+
default:
338+
logrus.Debugf("don't know how to convert memory.oom.group=%d; skipping (will still be applied to cgroupfs)", value)
339+
}
340+
}
341+
}
342+
330343
if err := startUnit(m.dbus, unitName, properties, pid == -1); err != nil {
331344
return fmt.Errorf("unable to start unit %q (properties %+v): %w", unitName, properties, err)
332345
}

0 commit comments

Comments
 (0)