Watch
0
0
Fork
You've already forked hyperhive
0

budgeted agent containers get no swap; export oom kills and swap use

This commit is contained in:
atlas 2026-11-11 01:58:30 +01:00
commit 4eb27046a0

View file

@ -225,6 +225,10 @@ const RESTART_SEC: u32 = 5;
/// weights. A weight of `None` means "not configured" and omits its line
/// entirely, so a request from a hive-c0re built before the weights
/// existed — or one whose nix option is `null` — renders no weight lines.
///
/// A finite `MemoryMax=` comes with `MemorySwapMax=0`: at the budget the
/// kernel kills inside the container instead of paging it out to host swap.
/// `MemoryMax=infinity` is no budget and leaves swap alone.
fn limits_dropin_body(
runtime_dir: &str,
rootfs_check: &str,
@ -238,6 +242,11 @@ fn limits_dropin_body(
// `format_push_string`, and a `write!` would need an unwrap.
let cpu_weight_line = cpu_weight.map_or_else(String::new, |w| format!("CPUWeight={w}\n"));
let io_weight_line = io_weight.map_or_else(String::new, |w| format!("IOWeight={w}\n"));
let swap_line = if memory_max == "infinity" {
""
} else {
"MemorySwapMax=0\n"
};
format!(
"[Unit]\n\
ConditionPathIsDirectory={runtime_dir}\n\
@ -248,6 +257,7 @@ fn limits_dropin_body(
ExecStartPre={rootfs_check}\n\
RestartSec={RESTART_SEC}\n\
MemoryMax={memory_max}\n\
{swap_line}\
CPUQuota={cpu_quota}\n\
{cpu_weight_line}{io_weight_line}"
)
@ -893,6 +903,7 @@ mod tests {
ExecStartPre=/store/hive-priv/bin/hive-priv check-rootfs h-iris\n\
RestartSec=5\n\
MemoryMax=4G\n\
MemorySwapMax=0\n\
CPUQuota=200%\n"
);
}
@ -924,6 +935,25 @@ mod tests {
}
}
/// A budgeted container gets no swap; an unbudgeted one keeps it. The
/// finite arm is the presence control for the infinity arm's absence.
#[test]
fn swap_is_off_exactly_when_the_memory_is_budgeted() {
for mem in ["4G", "512M", "50%", "8589934592"] {
let body = limits_dropin_body("/rt/x", CHECK, mem, "200%", None, None);
assert!(
body.contains(&format!("\nMemoryMax={mem}\nMemorySwapMax=0\n")),
"no swap line under a budget of {mem}: {body}"
);
assert_eq!(body.matches("MemorySwapMax=").count(), 1, "{body}");
}
let unbudgeted = limits_dropin_body("/rt/x", CHECK, "infinity", "200%", None, None);
assert!(
unbudgeted.contains("\nMemoryMax=infinity\n"),
"{unbudgeted}"
);
assert!(!unbudgeted.contains("MemorySwapMax"), "{unbudgeted}");
}
/// Weights are appended to the `[Service]` section, each omitted
/// independently when `None`.
#[test]