refactor(#2754): make the container weights Option, not a 0 sentinel

Encoding "not configured" as weight 0 worked (the writer omitted the
line) but the type lied: 0 is not a legal cgroup v2 weight, and every
reader had to know the sentinel. Use Option<u32> end to end instead —
wire type, priv_client, HiveEnv, drop-in writer — so "unset" is a state
of the type rather than a magic value.

The nix options become nullOr, keeping their default of 80; null now
expresses "leave the setting out of the drop-in entirely" declaratively,
which is the useful shape on a host whose IO scheduler ignores
io.weight anyway.

Backward compat is unchanged: the fields stay #[serde(default)], so a
request from an older hive-c0re deserialises to None and reproduces the
pre-weights drop-in byte for byte. The test that pins that now passes
None instead of 0.
This commit is contained in:
atlas 2026-07-27 10:55:29 +02:00
commit 5d3f2af75e
7 changed files with 59 additions and 51 deletions

View file

@ -87,12 +87,14 @@ pub struct Coordinator {
pub agent_memory_max: String,
/// Per-agent systemd `CPUWeight=` (cgroup v2 `cpu.weight`, 1..=10000).
/// Same drop-in. A *relative share* under contention, not a cap — see
/// the `agentCpuWeight` NixOS option for the full semantics.
pub agent_cpu_weight: u32,
/// the `agentCpuWeight` NixOS option for the full semantics. `None`
/// (option set to `null`) omits the setting: kernel default, and the
/// drop-in is byte-identical to the one written before weights existed.
pub agent_cpu_weight: Option<u32>,
/// Per-agent systemd `IOWeight=` (cgroup v2 `io.weight`, 1..=10000).
/// Same drop-in, same relative-share semantics as
/// Same drop-in, same relative-share and `None` semantics as
/// [`Self::agent_cpu_weight`].
pub agent_io_weight: u32,
pub agent_io_weight: Option<u32>,
/// Operator-tunable model→price table backing the hive-wide cost
/// estimate on the ST4TS tab. Set via `services.hyperhive.modelPrices`
/// and passed to `hive-c0re serve --model-prices <json>`. Models not
@ -220,10 +222,11 @@ pub struct HiveEnv {
pub agent_memory_max: String,
/// Per-agent systemd `CPUWeight=` — cgroup v2 `cpu.weight`, 1..=10000.
/// Relative share under contention, not a cap; see `agentCpuWeight`.
pub agent_cpu_weight: u32,
/// `None` = leave the setting out of the drop-in (kernel default).
pub agent_cpu_weight: Option<u32>,
/// Per-agent systemd `IOWeight=` — cgroup v2 `io.weight`, 1..=10000.
/// Same semantics as [`Self::agent_cpu_weight`]; see `agentIoWeight`.
pub agent_io_weight: u32,
pub agent_io_weight: Option<u32>,
}
impl Default for HiveEnv {
@ -244,8 +247,8 @@ impl Default for HiveEnv {
// Slightly below the kernel default of 100, so agent
// containers yield to host services + the infra containers
// (which carry no drop-in and stay at 100) under contention.
agent_cpu_weight: 80,
agent_io_weight: 80,
agent_cpu_weight: Some(80),
agent_io_weight: Some(80),
}
}
}

View file

@ -45,12 +45,14 @@ pub async fn write_dropins(name: &str, hive: &HiveEnv, paths: &AgentPaths) -> Re
/// The weights are hive-wide (`services.hyperhive.agentCpuWeight` /
/// `agentIoWeight`) — unlike the caps they have no per-agent override in
/// `meta/resource-limits.json`, so they come straight off [`HiveEnv`].
/// `None` (the nix option set to `null`) means the weight line is left out
/// and the container keeps the kernel default.
async fn set_resource_limits(
container: &str,
cpu_quota: &str,
memory_max: &str,
cpu_weight: u32,
io_weight: u32,
cpu_weight: Option<u32>,
io_weight: Option<u32>,
) -> Result<()> {
crate::priv_client::write_resource_limits(
container, memory_max, cpu_quota, cpu_weight, io_weight,

View file

@ -197,11 +197,15 @@ async fn main() -> Result<()> {
if let Some(v) = agent_memory_max {
sc.env.agent_memory_max = v;
}
// Passing the flag sets a weight; omitting it keeps whatever the
// config file says (including `null` = don't emit the setting).
// There's deliberately no flag spelling for "clear it" — that's
// what the nix option's `null` is for.
if let Some(v) = agent_cpu_weight {
sc.env.agent_cpu_weight = v;
sc.env.agent_cpu_weight = Some(v);
}
if let Some(v) = agent_io_weight {
sc.env.agent_io_weight = v;
sc.env.agent_io_weight = Some(v);
}
if let Some(v) = model_prices {
sc.model_prices =

View file

@ -174,8 +174,8 @@ pub async fn write_resource_limits(
container: &str,
memory_max: &str,
cpu_quota: &str,
cpu_weight: u32,
io_weight: u32,
cpu_weight: Option<u32>,
io_weight: Option<u32>,
) -> Result<()> {
ok(call(&PrivRequest::WriteResourceLimits {
container: container.to_owned(),