diff --git a/packages/envd/internal/services/cgroups/cgroup2.go b/packages/envd/internal/services/cgroups/cgroup2.go index aae10a6429..9a3e0b705d 100644 --- a/packages/envd/internal/services/cgroups/cgroup2.go +++ b/packages/envd/internal/services/cgroups/cgroup2.go @@ -104,6 +104,19 @@ func createCgroups(configs cgroup2Config) (map[ProcessType]int, error) { return results, nil } +// writeCgroupProp writes a cgroupfs property without O_CREATE so missing +// properties error out rather than being silently created on a tmpfs fallback. +func writeCgroupProp(path, value string) error { + f, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC, 0) + if err != nil { + return err + } + defer f.Close() + _, err = f.WriteString(value) + + return err +} + func createCgroup(fullPath string, properties map[string]string) (int, error) { if err := os.MkdirAll(fullPath, 0o755); err != nil { return -1, fmt.Errorf("failed to create cgroup root: %w", err) @@ -111,8 +124,14 @@ func createCgroup(fullPath string, properties map[string]string) (int, error) { var errs []error for name, value := range properties { - if err := os.WriteFile(filepath.Join(fullPath, name), []byte(value), 0o644); err != nil { - errs = append(errs, fmt.Errorf("failed to write cgroup property: %w", err)) + if err := writeCgroupProp(filepath.Join(fullPath, name), value); err != nil { + // Skip properties whose controller isn't enabled in subtree_control. + if errors.Is(err, os.ErrNotExist) || errors.Is(err, os.ErrPermission) { + fmt.Fprintf(os.Stderr, "cgroup property %q unavailable at %q, skipping\n", name, fullPath) + + continue + } + errs = append(errs, fmt.Errorf("failed to write cgroup property %q: %w", name, err)) } } if len(errs) > 0 { diff --git a/packages/envd/internal/services/process/handler/handler.go b/packages/envd/internal/services/process/handler/handler.go index 82e17d2796..af91bca1c2 100644 --- a/packages/envd/internal/services/process/handler/handler.go +++ b/packages/envd/internal/services/process/handler/handler.go @@ -96,12 +96,9 @@ func New( // User command string for logging (without the internal wrapper details). userCmd := strings.Join(append([]string{req.GetProcess().GetCmd()}, req.GetProcess().GetArgs()...), " ") - // Wrap the command in a shell that sets the OOM score and nice value before exec-ing the actual command. - // This eliminates the race window where grandchildren could inherit the parent's protected OOM score (-1000) - // or high CPU priority (nice -20) before the post-start calls had a chance to correct them. - // nice(1) applies a relative adjustment, so we compute the delta from the current (inherited) nice to the target. + // Wrap in a shell that resets oom_score_adj, ioprio (ionice best-effort/4), and nice. niceDelta := defaultNice - currentNice() - oomWrapperScript := fmt.Sprintf(`echo %d > /proc/$$/oom_score_adj && exec /usr/bin/nice -n %d "${@}"`, defaultOomScore, niceDelta) + oomWrapperScript := fmt.Sprintf(`echo %d > /proc/$$/oom_score_adj && exec /usr/bin/ionice -c 2 -n 4 /usr/bin/nice -n %d "${@}"`, defaultOomScore, niceDelta) wrapperArgs := append([]string{"-c", oomWrapperScript, "--", req.GetProcess().GetCmd()}, req.GetProcess().GetArgs()...) cmd := exec.CommandContext(ctx, "/bin/sh", wrapperArgs...) diff --git a/packages/envd/main.go b/packages/envd/main.go index c37da6ba33..8836720065 100644 --- a/packages/envd/main.go +++ b/packages/envd/main.go @@ -242,19 +242,22 @@ func createCgroupManager() (m cgroups.Manager) { opts := []cgroups.Cgroup2ManagerOption{ cgroups.WithCgroup2ProcessType(cgroups.ProcessTypePTY, "ptys", map[string]string{ - "cpu.weight": "200", // gets much preferred cpu access, to help keep these real time + "cpu.weight": "200", + "io.weight": "default 50", "memory.high": fmt.Sprintf("%d", memoryHigh), "memory.max": fmt.Sprintf("%d", memoryMax), }), cgroups.WithCgroup2ProcessType(cgroups.ProcessTypeSocat, "socats", map[string]string{ - "cpu.weight": "150", // gets slightly preferred cpu access + "cpu.weight": "150", + "io.weight": "default 50", "memory.min": fmt.Sprintf("%d", 5*megabyte), "memory.low": fmt.Sprintf("%d", 8*megabyte), }), cgroups.WithCgroup2ProcessType(cgroups.ProcessTypeUser, "user", map[string]string{ "memory.high": fmt.Sprintf("%d", memoryHigh), "memory.max": fmt.Sprintf("%d", memoryMax), - "cpu.weight": "50", // less than envd, and less than core processes that default to 100 + "cpu.weight": "50", + "io.weight": "default 10", }), } if cgroupRoot != "" { diff --git a/packages/envd/pkg/version.go b/packages/envd/pkg/version.go index 88bc32c769..4cfbb08b29 100644 --- a/packages/envd/pkg/version.go +++ b/packages/envd/pkg/version.go @@ -1,3 +1,3 @@ package pkg -const Version = "0.5.23" +const Version = "0.5.24" diff --git a/packages/orchestrator/pkg/template/build/core/rootfs/files/envd.service.tpl b/packages/orchestrator/pkg/template/build/core/rootfs/files/envd.service.tpl index 3fc1eab0fc..0f08c95f90 100644 --- a/packages/orchestrator/pkg/template/build/core/rootfs/files/envd.service.tpl +++ b/packages/orchestrator/pkg/template/build/core/rootfs/files/envd.service.tpl @@ -17,6 +17,8 @@ LimitCORE=infinity ExecStartPre=/bin/sh -c 'mountpoint -q /etc/ssl/certs || (mkdir -p /run/e2b/certs && mount --bind /run/e2b/certs /etc/ssl/certs) && ([ -s /etc/ssl/certs/ca-certificates.crt ] || update-ca-certificates)' ExecStart=/bin/bash -l -c "/usr/bin/envd" Nice=-20 +IOSchedulingClass=realtime +IOSchedulingPriority=4 OOMPolicy=continue OOMScoreAdjust=-1000 Environment="GOMEMLIMIT={{ .MemoryLimit }}MiB" @@ -26,6 +28,8 @@ MemoryMin=50M MemoryLow=100M CPUAccounting=yes CPUWeight=1000 +IOAccounting=yes +IOWeight=10000 [Install] WantedBy=multi-user.target