diff --git a/CHANGELOG.md b/CHANGELOG.md index a810474ce..aae5d9f46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,9 +18,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 github.com/moby/sys/user for that. (#3999) ### Fixed - * `runc exec -p` no longer ignores specified `ioPriority` setting. - Similarly, libcontainer's `Container.Start` and `Container.Run` - methods no longer ignore `Process.IOPriority` setting. (#4585) + * `runc exec -p` no longer ignores specified `ioPriority` and `scheduler` + settings. Similarly, libcontainer's `Container.Start` and `Container.Run` + methods no longer ignore `Process.IOPriority` and `Process.Scheduler` + settings. (#4585) ## [1.2.0] - 2024-10-22 diff --git a/libcontainer/container_linux.go b/libcontainer/container_linux.go index 8a9cf0786..54a0eaafe 100644 --- a/libcontainer/container_linux.go +++ b/libcontainer/container_linux.go @@ -708,6 +708,7 @@ func (c *Container) newInitConfig(process *Process) *initConfig { ProcessLabel: c.config.ProcessLabel, Rlimits: c.config.Rlimits, IOPriority: c.config.IOPriority, + Scheduler: c.config.Scheduler, CreateConsole: process.ConsoleSocket != nil, ConsoleWidth: process.ConsoleWidth, ConsoleHeight: process.ConsoleHeight, @@ -733,6 +734,9 @@ func (c *Container) newInitConfig(process *Process) *initConfig { if process.IOPriority != nil { cfg.IOPriority = process.IOPriority } + if process.Scheduler != nil { + cfg.Scheduler = process.Scheduler + } // Set misc properties. diff --git a/libcontainer/init_linux.go b/libcontainer/init_linux.go index 5976b2933..f78e56175 100644 --- a/libcontainer/init_linux.go +++ b/libcontainer/init_linux.go @@ -82,6 +82,7 @@ type initConfig struct { ProcessLabel string `json:"process_label"` Rlimits []configs.Rlimit `json:"rlimits"` IOPriority *configs.IOPriority `json:"io_priority,omitempty"` + Scheduler *configs.Scheduler `json:"scheduler,omitempty"` // Miscellaneous properties, filled in by [Container.newInitConfig] // unless documented otherwise. @@ -607,7 +608,7 @@ func setupRlimits(limits []configs.Rlimit, pid int) error { return nil } -func setupScheduler(config *configs.Config) error { +func setupScheduler(config *initConfig) error { if config.Scheduler == nil { return nil } @@ -616,7 +617,7 @@ func setupScheduler(config *configs.Config) error { return err } if err := unix.SchedSetAttr(0, attr, 0); err != nil { - if errors.Is(err, unix.EPERM) && config.Cgroups.CpusetCpus != "" { + if errors.Is(err, unix.EPERM) && config.Config.Cgroups.CpusetCpus != "" { return errors.New("process scheduler can't be used together with AllowedCPUs") } return fmt.Errorf("error setting scheduler: %w", err) diff --git a/libcontainer/process.go b/libcontainer/process.go index 09e57ae46..0e24c548e 100644 --- a/libcontainer/process.go +++ b/libcontainer/process.go @@ -112,6 +112,9 @@ type Process struct { // For cgroup v2, the only key allowed is "". SubCgroupPaths map[string]string + // Scheduler represents the scheduling attributes for a process. + // + // If not empty, takes precedence over container's [configs.Config.Scheduler]. Scheduler *configs.Scheduler // IOPriority is a process I/O priority. diff --git a/libcontainer/setns_init_linux.go b/libcontainer/setns_init_linux.go index 505af1d08..0a79f197e 100644 --- a/libcontainer/setns_init_linux.go +++ b/libcontainer/setns_init_linux.go @@ -71,7 +71,7 @@ func (l *linuxSetnsInit) Init() error { unix.Umask(int(*l.config.Config.Umask)) } - if err := setupScheduler(l.config.Config); err != nil { + if err := setupScheduler(l.config); err != nil { return err } diff --git a/libcontainer/standard_init_linux.go b/libcontainer/standard_init_linux.go index 0c83db30f..384750bf8 100644 --- a/libcontainer/standard_init_linux.go +++ b/libcontainer/standard_init_linux.go @@ -155,7 +155,7 @@ func (l *linuxStandardInit) Init() error { } } - if err := setupScheduler(l.config.Config); err != nil { + if err := setupScheduler(l.config); err != nil { return err } diff --git a/tests/integration/scheduler.bats b/tests/integration/scheduler.bats index b7cd96f88..6c80d8642 100644 --- a/tests/integration/scheduler.bats +++ b/tests/integration/scheduler.bats @@ -12,17 +12,49 @@ function teardown() { } @test "scheduler is applied" { - update_config ' .process.scheduler = {"policy": "SCHED_DEADLINE", "nice": 19, "priority": 0, "runtime": 42000, "deadline": 1000000, "period": 1000000, }' + update_config ' .process.scheduler = { + "policy": "SCHED_BATCH", + "priority": 0, + "nice": 19 + }' runc run -d --console-socket "$CONSOLE_SOCKET" test_scheduler [ "$status" -eq 0 ] + # Check init settings. runc exec test_scheduler chrt -p 1 [ "$status" -eq 0 ] - - [[ "${lines[0]}" == *"scheduling policy: SCHED_DEADLINE" ]] + [[ "${lines[0]}" == *"scheduling policy: SCHED_BATCH" ]] [[ "${lines[1]}" == *"priority: 0" ]] - [[ "${lines[2]}" == *"runtime/deadline/period parameters: 42000/1000000/1000000" ]] + + # Check exec settings derived from config.json. + runc exec test_scheduler sh -c 'chrt -p $$' + [ "$status" -eq 0 ] + [[ "${lines[0]}" == *"scheduling policy: SCHED_BATCH" ]] + [[ "${lines[1]}" == *"priority: 0" ]] + + # Another exec, with different scheduler settings. + proc=' +{ + "terminal": false, + "args": [ "/bin/sleep", "600" ], + "cwd": "/", + "scheduler": { + "policy": "SCHED_DEADLINE", + "flags": [ "SCHED_FLAG_RESET_ON_FORK" ], + "nice": 19, + "priority": 0, + "runtime": 42000, + "deadline": 100000, + "period": 1000000 + } +}' + __runc exec -d --pid-file pid.txt --process <(echo "$proc") test_scheduler + + run chrt -p "$(cat pid.txt)" + [[ "${lines[0]}" == *"scheduling policy: SCHED_DEADLINE|SCHED_RESET_ON_FORK" ]] + [[ "${lines[1]}" == *"priority: 0" ]] + [[ "${lines[2]}" == *"runtime/deadline/period parameters: 42000/100000/1000000" ]] } # Checks that runc emits a specific error when scheduling policy is used