Skip to content

Commit c48b5d8

Browse files
committed
solver: fix resource limit merge semantics for CpuShares, MemorySwap, and CpuPeriod
Signed-off-by: Jiří Moravčík <jiri.moravcik@gmail.com>
1 parent 26281fd commit c48b5d8

File tree

2 files changed

+91
-4
lines changed

2 files changed

+91
-4
lines changed

solver/jobs.go

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1318,8 +1318,8 @@ func mergeLinuxResourcesRelaxed(a, b *pb.LinuxResources) *pb.LinuxResources {
13181318
}
13191319
return &pb.LinuxResources{
13201320
Memory: relaxedLimit(a.Memory, b.Memory),
1321-
MemorySwap: relaxedLimit(a.MemorySwap, b.MemorySwap),
1322-
CpuShares: relaxedLimit(a.CpuShares, b.CpuShares),
1321+
MemorySwap: relaxedMemorySwap(a.MemorySwap, b.MemorySwap),
1322+
CpuShares: relaxedWeight(a.CpuShares, b.CpuShares),
13231323
CpuPeriod: relaxedCPUPeriod(a.CpuPeriod, b.CpuPeriod, a.CpuQuota, b.CpuQuota),
13241324
CpuQuota: relaxedLimit(a.CpuQuota, b.CpuQuota),
13251325
CpusetCpus: relaxedCpuset(a.CpusetCpus, b.CpusetCpus),
@@ -1336,12 +1336,51 @@ func relaxedLimit[T int64 | uint64](a, b T) T {
13361336
return max(a, b)
13371337
}
13381338

1339+
// relaxedMemorySwap returns the more relaxed of two memory-swap limits.
1340+
// Per Docker semantics: 0 means "unset" (preserve the other value),
1341+
// -1 means "unlimited swap" (most relaxed), and positive values are limits
1342+
// where higher = more relaxed.
1343+
func relaxedMemorySwap(a, b int64) int64 {
1344+
if a == 0 {
1345+
return b
1346+
}
1347+
if b == 0 {
1348+
return a
1349+
}
1350+
// -1 means unlimited swap, which is the most relaxed.
1351+
if a == -1 || b == -1 {
1352+
return -1
1353+
}
1354+
return max(a, b)
1355+
}
1356+
1357+
// relaxedWeight returns the more relaxed of two resource weights (like CPU shares).
1358+
// Unlike limits, 0 means "unset" (use kernel default), not "unlimited".
1359+
// Between two explicit values, higher weight = more resources = more relaxed.
1360+
func relaxedWeight(a, b uint64) uint64 {
1361+
if a == 0 {
1362+
return b
1363+
}
1364+
if b == 0 {
1365+
return a
1366+
}
1367+
return max(a, b)
1368+
}
1369+
13391370
// relaxedCPUPeriod returns the CPU period corresponding to the more relaxed
13401371
// CPU quota. Period is meaningless without quota, so we pick the period from
1341-
// whichever side has the more relaxed quota. When both quotas are equal
1342-
// (including both zero/unlimited), periodB is returned arbitrarily.
1372+
// whichever side has the more relaxed quota. When both quotas are equal,
1373+
// shorter period is more relaxed (same quota executes more frequently).
13431374
func relaxedCPUPeriod(periodA, periodB uint64, quotaA, quotaB int64) uint64 {
13441375
relaxedQ := relaxedLimit(quotaA, quotaB)
1376+
if quotaA == quotaB {
1377+
// Equal quotas: shorter period = more CPU time = more relaxed.
1378+
if periodA == 0 || periodB == 0 {
1379+
// 0 means unset; preserve the set value.
1380+
return max(periodA, periodB)
1381+
}
1382+
return min(periodA, periodB)
1383+
}
13451384
if relaxedQ == quotaB {
13461385
return periodB
13471386
}

solver/merge_linux_resources_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,53 @@ func TestMergeLinuxResourcesRelaxed(t *testing.T) {
4545
require.Equal(t, int64(0), result.Memory)
4646
})
4747

48+
t.Run("zero memory swap means unset, preserves non-zero", func(t *testing.T) {
49+
a := &pb.LinuxResources{MemorySwap: 256 * 1024 * 1024}
50+
b := &pb.LinuxResources{MemorySwap: 0}
51+
result := mergeLinuxResourcesRelaxed(a, b)
52+
require.Equal(t, int64(256*1024*1024), result.MemorySwap)
53+
54+
// Reverse order
55+
result = mergeLinuxResourcesRelaxed(b, a)
56+
require.Equal(t, int64(256*1024*1024), result.MemorySwap)
57+
})
58+
59+
t.Run("memory swap -1 means unlimited and wins", func(t *testing.T) {
60+
a := &pb.LinuxResources{MemorySwap: 256 * 1024 * 1024}
61+
b := &pb.LinuxResources{MemorySwap: -1}
62+
result := mergeLinuxResourcesRelaxed(a, b)
63+
require.Equal(t, int64(-1), result.MemorySwap)
64+
65+
// Reverse order
66+
result = mergeLinuxResourcesRelaxed(b, a)
67+
require.Equal(t, int64(-1), result.MemorySwap)
68+
})
69+
70+
t.Run("higher memory swap wins", func(t *testing.T) {
71+
a := &pb.LinuxResources{MemorySwap: 128 * 1024 * 1024}
72+
b := &pb.LinuxResources{MemorySwap: 256 * 1024 * 1024}
73+
result := mergeLinuxResourcesRelaxed(a, b)
74+
require.Equal(t, int64(256*1024*1024), result.MemorySwap)
75+
})
76+
4877
t.Run("higher cpu shares wins", func(t *testing.T) {
4978
a := &pb.LinuxResources{CpuShares: 256}
5079
b := &pb.LinuxResources{CpuShares: 512}
5180
result := mergeLinuxResourcesRelaxed(a, b)
5281
require.Equal(t, uint64(512), result.CpuShares)
5382
})
5483

84+
t.Run("zero cpu shares means unset, preserves non-zero", func(t *testing.T) {
85+
a := &pb.LinuxResources{CpuShares: 512}
86+
b := &pb.LinuxResources{CpuShares: 0}
87+
result := mergeLinuxResourcesRelaxed(a, b)
88+
require.Equal(t, uint64(512), result.CpuShares)
89+
90+
// Reverse order
91+
result = mergeLinuxResourcesRelaxed(b, a)
92+
require.Equal(t, uint64(512), result.CpuShares)
93+
})
94+
5595
t.Run("higher cpu quota wins", func(t *testing.T) {
5696
a := &pb.LinuxResources{CpuQuota: 50000, CpuPeriod: 100000}
5797
b := &pb.LinuxResources{CpuQuota: 80000, CpuPeriod: 100000}
@@ -75,6 +115,14 @@ func TestMergeLinuxResourcesRelaxed(t *testing.T) {
75115
require.Equal(t, uint64(200000), result.CpuPeriod)
76116
})
77117

118+
t.Run("equal quotas picks shorter period (more relaxed)", func(t *testing.T) {
119+
a := &pb.LinuxResources{CpuQuota: 50000, CpuPeriod: 100000}
120+
b := &pb.LinuxResources{CpuQuota: 50000, CpuPeriod: 200000}
121+
result := mergeLinuxResourcesRelaxed(a, b)
122+
require.Equal(t, int64(50000), result.CpuQuota)
123+
require.Equal(t, uint64(100000), result.CpuPeriod) // shorter period = more CPU
124+
})
125+
78126
t.Run("empty cpuset string means unset and wins", func(t *testing.T) {
79127
a := &pb.LinuxResources{CpusetCpus: "0-3"}
80128
b := &pb.LinuxResources{CpusetCpus: ""}

0 commit comments

Comments
 (0)