aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2026-03-09 18:33:49 +0900
committerOphestra <cat@gensokyo.uk>2026-03-09 18:38:31 +0900
commit19a2737148609f0b40ea31cf5cd0cb809b60722f (patch)
treed1317481736578f02e81a7a9ce2d50c8776e3d40
parentbaf2def9cc201ee4ae5bab2d31e35c7904d090f9 (diff)
container: sched policy string representation
This also uses priority obtained via sched_get_priority_min, and improves bounds checking. Signed-off-by: Ophestra <cat@gensokyo.uk>
-rw-r--r--container/container.go17
-rw-r--r--container/syscall.go123
-rw-r--r--container/syscall_test.go100
-rw-r--r--internal/pkg/exec.go2
4 files changed, 235 insertions, 7 deletions
diff --git a/container/container.go b/container/container.go
index 8b5ec01d..e7fd8cee 100644
--- a/container/container.go
+++ b/container/container.go
@@ -40,7 +40,7 @@ type (
AllowOrphan bool
// Scheduling policy to set via sched_setscheduler(2). The zero value
// skips this call. Supported policies are [SCHED_BATCH], [SCHED_IDLE].
- SchedPolicy int
+ SchedPolicy SchedPolicy
// Cgroup fd, nil to disable.
Cgroup *int
// ExtraFiles passed through to initial process in the container, with
@@ -373,12 +373,23 @@ func (p *Container) Start() error {
// sched_setscheduler: thread-directed but acts on all processes
// created from the calling thread
- if p.SchedPolicy > 0 {
+ if p.SchedPolicy > 0 && p.SchedPolicy <= _SCHED_LAST {
+ var param schedParam
+ if priority, err := p.SchedPolicy.GetPriorityMin(); err != nil {
+ return &StartError{
+ Fatal: true,
+ Step: "get minimum priority",
+ Err: err,
+ }
+ } else {
+ param.priority = priority
+ }
+
p.msg.Verbosef("setting scheduling policy %d", p.SchedPolicy)
if err := schedSetscheduler(
0, // calling thread
p.SchedPolicy,
- &schedParam{0},
+ &param,
); err != nil {
return &StartError{
Fatal: true,
diff --git a/container/syscall.go b/container/syscall.go
index 5f392c35..4af60366 100644
--- a/container/syscall.go
+++ b/container/syscall.go
@@ -1,6 +1,9 @@
package container
import (
+ "encoding"
+ "strconv"
+ "sync"
. "syscall"
"unsafe"
@@ -43,18 +46,132 @@ func Isatty(fd int) bool {
return r == 0
}
+// SchedPolicy denotes a scheduling policy defined in include/uapi/linux/sched.h.
+type SchedPolicy int
+
// include/uapi/linux/sched.h
const (
- SCHED_NORMAL = iota
+ SCHED_NORMAL SchedPolicy = iota
SCHED_FIFO
SCHED_RR
SCHED_BATCH
- _ // SCHED_ISO: reserved but not implemented yet
+ _SCHED_ISO // SCHED_ISO: reserved but not implemented yet
SCHED_IDLE
SCHED_DEADLINE
SCHED_EXT
+
+ _SCHED_LAST SchedPolicy = iota - 1
)
+var _ encoding.TextMarshaler = _SCHED_LAST
+var _ encoding.TextUnmarshaler = new(SchedPolicy)
+
+// String returns a unique representation of policy, also used in encoding.
+func (policy SchedPolicy) String() string {
+ switch policy {
+ case SCHED_NORMAL:
+ return ""
+ case SCHED_FIFO:
+ return "fifo"
+ case SCHED_RR:
+ return "rr"
+ case SCHED_BATCH:
+ return "batch"
+ case SCHED_IDLE:
+ return "idle"
+ case SCHED_DEADLINE:
+ return "deadline"
+ case SCHED_EXT:
+ return "ext"
+
+ default:
+ return "invalid policy " + strconv.Itoa(int(policy))
+ }
+}
+
+// MarshalText performs bounds checking and returns the result of String.
+func (policy SchedPolicy) MarshalText() ([]byte, error) {
+ if policy == _SCHED_ISO || policy < 0 || policy > _SCHED_LAST {
+ return nil, EINVAL
+ }
+ return []byte(policy.String()), nil
+}
+
+// InvalidSchedPolicyError is an invalid string representation of a [SchedPolicy].
+type InvalidSchedPolicyError string
+
+func (InvalidSchedPolicyError) Unwrap() error { return EINVAL }
+func (e InvalidSchedPolicyError) Error() string {
+ return "invalid scheduling policy " + strconv.Quote(string(e))
+}
+
+// UnmarshalText is the inverse of MarshalText.
+func (policy *SchedPolicy) UnmarshalText(text []byte) error {
+ switch string(text) {
+ case "fifo":
+ *policy = SCHED_FIFO
+ case "rr":
+ *policy = SCHED_RR
+ case "batch":
+ *policy = SCHED_BATCH
+ case "idle":
+ *policy = SCHED_IDLE
+ case "deadline":
+ *policy = SCHED_DEADLINE
+ case "ext":
+ *policy = SCHED_EXT
+
+ case "":
+ *policy = 0
+ return nil
+ default:
+ return InvalidSchedPolicyError(text)
+ }
+ return nil
+}
+
+// for sched_get_priority_max and sched_get_priority_min
+var (
+ schedPriority [_SCHED_LAST + 1][2]std.Int
+ schedPriorityErr [_SCHED_LAST + 1][2]error
+ schedPriorityOnce [_SCHED_LAST + 1][2]sync.Once
+)
+
+// GetPriorityMax returns the maximum priority value that can be used with the
+// scheduling algorithm identified by policy.
+func (policy SchedPolicy) GetPriorityMax() (std.Int, error) {
+ schedPriorityOnce[policy][0].Do(func() {
+ priority, _, errno := Syscall(
+ SYS_SCHED_GET_PRIORITY_MAX,
+ uintptr(policy),
+ 0, 0,
+ )
+ schedPriority[policy][0] = std.Int(priority)
+ if schedPriority[policy][0] < 0 {
+ schedPriorityErr[policy][0] = errno
+ }
+ })
+ return schedPriority[policy][0], schedPriorityErr[policy][0]
+}
+
+// GetPriorityMin returns the minimum priority value that can be used with the
+// scheduling algorithm identified by policy.
+func (policy SchedPolicy) GetPriorityMin() (std.Int, error) {
+ schedPriorityOnce[policy][1].Do(func() {
+ priority, _, errno := Syscall(
+ SYS_SCHED_GET_PRIORITY_MIN,
+ uintptr(policy),
+ 0, 0,
+ )
+ schedPriority[policy][1] = std.Int(priority)
+ if schedPriority[policy][1] < 0 {
+ schedPriorityErr[policy][1] = errno
+ }
+ })
+ return schedPriority[policy][1], schedPriorityErr[policy][1]
+
+}
+
// schedParam is equivalent to struct sched_param from include/linux/sched.h.
type schedParam struct {
// sched_priority
@@ -74,7 +191,7 @@ type schedParam struct {
// this if you do not have something similar in place!
//
// [very subtle to use correctly]: https://www.openwall.com/lists/musl/2016/03/01/4
-func schedSetscheduler(tid, policy int, param *schedParam) error {
+func schedSetscheduler(tid int, policy SchedPolicy, param *schedParam) error {
if r, _, errno := Syscall(
SYS_SCHED_SETSCHEDULER,
uintptr(tid),
diff --git a/container/syscall_test.go b/container/syscall_test.go
new file mode 100644
index 00000000..337d6450
--- /dev/null
+++ b/container/syscall_test.go
@@ -0,0 +1,100 @@
+package container_test
+
+import (
+ "encoding/json"
+ "errors"
+ "math"
+ "reflect"
+ "syscall"
+ "testing"
+
+ "hakurei.app/container"
+ "hakurei.app/container/std"
+)
+
+func TestSchedPolicyJSON(t *testing.T) {
+ t.Parallel()
+
+ testCases := []struct {
+ policy container.SchedPolicy
+ want string
+ encodeErr error
+ decodeErr error
+ }{
+ {container.SCHED_NORMAL, `""`, nil, nil},
+ {container.SCHED_FIFO, `"fifo"`, nil, nil},
+ {container.SCHED_RR, `"rr"`, nil, nil},
+ {container.SCHED_BATCH, `"batch"`, nil, nil},
+ {4, `"invalid policy 4"`, syscall.EINVAL, container.InvalidSchedPolicyError("invalid policy 4")},
+ {container.SCHED_IDLE, `"idle"`, nil, nil},
+ {container.SCHED_DEADLINE, `"deadline"`, nil, nil},
+ {container.SCHED_EXT, `"ext"`, nil, nil},
+ {math.MaxInt, `"iso"`, syscall.EINVAL, container.InvalidSchedPolicyError("iso")},
+ }
+ for _, tc := range testCases {
+ name := tc.policy.String()
+ if tc.policy == container.SCHED_NORMAL {
+ name = "normal"
+ }
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got, err := json.Marshal(tc.policy)
+ if !errors.Is(err, tc.encodeErr) {
+ t.Fatalf("Marshal: error = %v, want %v", err, tc.encodeErr)
+ }
+ if err == nil && string(got) != tc.want {
+ t.Fatalf("Marshal: %s, want %s", string(got), tc.want)
+ }
+
+ var v container.SchedPolicy
+ if err = json.Unmarshal([]byte(tc.want), &v); !reflect.DeepEqual(err, tc.decodeErr) {
+ t.Fatalf("Unmarshal: error = %v, want %v", err, tc.decodeErr)
+ }
+ if err == nil && v != tc.policy {
+ t.Fatalf("Unmarshal: %d, want %d", v, tc.policy)
+ }
+ })
+ }
+}
+
+func TestSchedPolicyMinMax(t *testing.T) {
+ t.Parallel()
+
+ testCases := []struct {
+ policy container.SchedPolicy
+ min, max std.Int
+ err error
+ }{
+ {container.SCHED_NORMAL, 0, 0, nil},
+ {container.SCHED_FIFO, 1, 99, nil},
+ {container.SCHED_RR, 1, 99, nil},
+ {container.SCHED_BATCH, 0, 0, nil},
+ {4, -1, -1, syscall.EINVAL},
+ {container.SCHED_IDLE, 0, 0, nil},
+ {container.SCHED_DEADLINE, 0, 0, nil},
+ {container.SCHED_EXT, 0, 0, nil},
+ }
+ for _, tc := range testCases {
+ name := tc.policy.String()
+ if tc.policy == container.SCHED_NORMAL {
+ name = "normal"
+ }
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ if priority, err := tc.policy.GetPriorityMax(); !reflect.DeepEqual(err, tc.err) {
+ t.Fatalf("GetPriorityMax: error = %v, want %v", err, tc.err)
+ } else if priority != tc.max {
+ t.Fatalf("GetPriorityMax: %d, want %d", priority, tc.max)
+ }
+ if priority, err := tc.policy.GetPriorityMin(); !reflect.DeepEqual(err, tc.err) {
+ t.Fatalf("GetPriorityMin: error = %v, want %v", err, tc.err)
+ } else if priority != tc.min {
+ t.Fatalf("GetPriorityMin: %d, want %d", priority, tc.min)
+ }
+ })
+ }
+}
diff --git a/internal/pkg/exec.go b/internal/pkg/exec.go
index eb72ccbd..c7248f1a 100644
--- a/internal/pkg/exec.go
+++ b/internal/pkg/exec.go
@@ -40,7 +40,7 @@ type ExecPath struct {
}
// SchedPolicy is the [container] scheduling policy.
-var SchedPolicy int
+var SchedPolicy container.SchedPolicy
// PromoteLayers returns artifacts with identical-by-content layers promoted to
// the highest priority instance, as if mounted via [ExecPath].