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:
parent
e407fa93df
commit
5d3f2af75e
7 changed files with 59 additions and 51 deletions
|
|
@ -537,8 +537,8 @@ fn chmod_socket_dir(agent_name: &str, mode: u32) -> Result<(String, String)> {
|
|||
/// `CPUQuota=` are hard caps that throttle even on an idle host;
|
||||
/// `CPUWeight=` / `IOWeight=` are cgroup v2 relative shares that only
|
||||
/// decide who yields *under contention*. A zero weight means "not
|
||||
/// configured" and omits the line, so a hive-c0re built before the weights
|
||||
/// existed keeps producing the old two-line drop-in.
|
||||
/// configured" (`None`) and omits the line, so a hive-c0re built before the
|
||||
/// weights existed keeps producing the old two-line drop-in.
|
||||
///
|
||||
/// The condition causes systemd to *skip* (not *fail*) the unit when the
|
||||
/// bind-mount source dir is absent — result is `condition`, which does not
|
||||
|
|
@ -550,8 +550,8 @@ 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<(String, String)> {
|
||||
validate_container_system_name(container)?;
|
||||
// Derive the logical agent name (strip h- prefix) to form the runtime
|
||||
|
|
@ -572,29 +572,21 @@ fn write_resource_limits(
|
|||
/// `[Unit]`: the condition is checked at start time — it skips (not fails)
|
||||
/// the unit when the MCP socket dir is absent, avoiding restart loops.
|
||||
/// `[Service]`: the hard caps first, then the relative weights. A weight of
|
||||
/// `0` means "not configured" and omits its line entirely, so a request from
|
||||
/// a hive-c0re built before the weights existed reproduces the old
|
||||
/// two-setting drop-in byte for byte.
|
||||
/// `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` — reproduces the old two-setting drop-in byte for byte.
|
||||
fn limits_dropin_body(
|
||||
runtime_dir: &str,
|
||||
memory_max: &str,
|
||||
cpu_quota: &str,
|
||||
cpu_weight: u32,
|
||||
io_weight: u32,
|
||||
cpu_weight: Option<u32>,
|
||||
io_weight: Option<u32>,
|
||||
) -> String {
|
||||
// Built as two possibly-empty lines rather than pushed onto the
|
||||
// string: `format!` appended to a `String` trips clippy::pedantic's
|
||||
// `format_push_string`, and a `write!` would need an unwrap.
|
||||
let cpu_weight_line = if cpu_weight > 0 {
|
||||
format!("CPUWeight={cpu_weight}\n")
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
let io_weight_line = if io_weight > 0 {
|
||||
format!("IOWeight={io_weight}\n")
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
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"));
|
||||
format!(
|
||||
"[Unit]\n\
|
||||
ConditionPathIsDirectory={runtime_dir}\n\
|
||||
|
|
@ -2231,13 +2223,13 @@ mod tests {
|
|||
use std::path::PathBuf;
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
/// A zero weight is "not configured": the drop-in must come out
|
||||
/// An unset weight is "not configured": the drop-in must come out
|
||||
/// byte-identical to the pre-weights two-setting body, so a hive-c0re
|
||||
/// older than this field can't change what lands on disk.
|
||||
#[test]
|
||||
fn zero_weights_reproduce_the_pre_weights_dropin() {
|
||||
fn unset_weights_reproduce_the_pre_weights_dropin() {
|
||||
assert_eq!(
|
||||
limits_dropin_body("/run/hyperhive/agents/iris", "4G", "200%", 0, 0),
|
||||
limits_dropin_body("/run/hyperhive/agents/iris", "4G", "200%", None, None),
|
||||
"[Unit]\n\
|
||||
ConditionPathIsDirectory=/run/hyperhive/agents/iris\n\
|
||||
\n\
|
||||
|
|
@ -2248,23 +2240,23 @@ mod tests {
|
|||
}
|
||||
|
||||
/// Weights are appended to the `[Service]` section, each omitted
|
||||
/// independently when zero.
|
||||
/// independently when `None`.
|
||||
#[test]
|
||||
fn weights_are_emitted_only_when_set() {
|
||||
let both = limits_dropin_body("/rt/x", "4G", "200%", 80, 80);
|
||||
let both = limits_dropin_body("/rt/x", "4G", "200%", Some(80), Some(80));
|
||||
assert!(
|
||||
both.ends_with("CPUQuota=200%\nCPUWeight=80\nIOWeight=80\n"),
|
||||
"{both}"
|
||||
);
|
||||
|
||||
let cpu_only = limits_dropin_body("/rt/x", "4G", "200%", 80, 0);
|
||||
let cpu_only = limits_dropin_body("/rt/x", "4G", "200%", Some(80), None);
|
||||
assert!(
|
||||
cpu_only.ends_with("CPUQuota=200%\nCPUWeight=80\n"),
|
||||
"{cpu_only}"
|
||||
);
|
||||
assert!(!cpu_only.contains("IOWeight"), "{cpu_only}");
|
||||
|
||||
let io_only = limits_dropin_body("/rt/x", "4G", "200%", 0, 80);
|
||||
let io_only = limits_dropin_body("/rt/x", "4G", "200%", None, Some(80));
|
||||
assert!(
|
||||
io_only.ends_with("CPUQuota=200%\nIOWeight=80\n"),
|
||||
"{io_only}"
|
||||
|
|
|
|||
Loading…
Reference in a new issue