use cellos_core::types::RunLimits;
pub const DEFAULT_CPU_PERIOD_MICROS: u64 = 100_000;
pub fn cpu_max_to_write(
spec_limits: Option<&RunLimits>,
env_cpu_max: Option<&str>,
) -> Option<String> {
if let Some(cpu) = spec_limits.and_then(|l| l.cpu_max.as_ref()) {
if cpu.quota_micros == 0 {
return None;
}
let period = cpu
.period_micros
.unwrap_or(DEFAULT_CPU_PERIOD_MICROS)
.max(1);
let quota = cpu.quota_micros;
return Some(format!("{quota} {period}"));
}
let raw = env_cpu_max?.trim();
if raw.is_empty() {
return None;
}
normalize_env_cpu_max(raw)
}
fn normalize_env_cpu_max(raw: &str) -> Option<String> {
let mut parts = raw.split_ascii_whitespace();
let first = parts.next()?;
let second = parts.next();
if parts.next().is_some() {
return None; }
let quota = match first {
"max" => "max".to_string(),
n => {
let v: u64 = n.parse().ok()?;
if v == 0 {
return None;
}
v.to_string()
}
};
let period: u64 = match second {
Some(p) => {
let v: u64 = p.parse().ok()?;
if v == 0 {
return None;
}
v
}
None => DEFAULT_CPU_PERIOD_MICROS,
};
Some(format!("{quota} {period}"))
}
pub fn cpu_max_env_validation_error(env_cpu_max: Option<&str>) -> Option<&'static str> {
let raw = env_cpu_max?.trim();
if raw.is_empty() {
return None;
}
if normalize_env_cpu_max(raw).is_none() {
Some("expected `max`, `<quota_micros>`, `max <period_micros>`, or `<quota_micros> <period_micros>` (each integer >= 1)")
} else {
None
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum CpuMaxApplyOutcome {
Wrote {
source: CpuMaxSource,
payload: String,
},
Skipped,
InvalidEnvOverride,
WriteError(String),
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum CpuMaxSource {
Spec,
EnvOverride,
}
pub fn apply_cpu_max_to_leaf(
leaf: &std::path::Path,
spec_limits: Option<&RunLimits>,
env_cpu_max: Option<&str>,
) -> CpuMaxApplyOutcome {
if let Some(payload) = cpu_max_to_write(spec_limits, env_cpu_max) {
let source = if spec_limits.and_then(|l| l.cpu_max.as_ref()).is_some() {
CpuMaxSource::Spec
} else {
CpuMaxSource::EnvOverride
};
let target = leaf.join("cpu.max");
match std::fs::write(&target, format!("{payload}\n")) {
Ok(()) => CpuMaxApplyOutcome::Wrote { source, payload },
Err(e) => CpuMaxApplyOutcome::WriteError(format!("{}: {e}", target.display())),
}
} else if spec_limits.and_then(|l| l.cpu_max.as_ref()).is_none()
&& cpu_max_env_validation_error(env_cpu_max).is_some()
{
CpuMaxApplyOutcome::InvalidEnvOverride
} else {
CpuMaxApplyOutcome::Skipped
}
}
#[cfg(test)]
mod tests {
use super::*;
use cellos_core::types::{RunCpuMax, RunLimits};
fn limits_with_cpu(quota: u64, period: Option<u64>) -> RunLimits {
RunLimits {
memory_max_bytes: None,
cpu_max: Some(RunCpuMax {
quota_micros: quota,
period_micros: period,
}),
graceful_shutdown_seconds: None,
}
}
#[test]
fn spec_with_quota_and_period_wins() {
let l = limits_with_cpu(50_000, Some(100_000));
assert_eq!(
cpu_max_to_write(Some(&l), Some("max 200000")),
Some("50000 100000".to_string())
);
}
#[test]
fn spec_quota_only_uses_default_period() {
let l = limits_with_cpu(75_000, None);
assert_eq!(
cpu_max_to_write(Some(&l), None),
Some("75000 100000".to_string())
);
}
#[test]
fn spec_quota_zero_returns_none_defense_in_depth() {
let l = limits_with_cpu(0, None);
assert_eq!(cpu_max_to_write(Some(&l), Some("50000 100000")), None);
}
#[test]
fn env_override_used_when_spec_absent() {
assert_eq!(
cpu_max_to_write(None, Some("50000 100000")),
Some("50000 100000".to_string())
);
}
#[test]
fn env_max_alone_normalizes_with_default_period() {
assert_eq!(
cpu_max_to_write(None, Some("max")),
Some("max 100000".to_string())
);
}
#[test]
fn env_max_with_period() {
assert_eq!(
cpu_max_to_write(None, Some("max 200000")),
Some("max 200000".to_string())
);
}
#[test]
fn env_quota_alone_uses_default_period() {
assert_eq!(
cpu_max_to_write(None, Some("25000")),
Some("25000 100000".to_string())
);
}
#[test]
fn env_whitespace_collapsed() {
assert_eq!(
cpu_max_to_write(None, Some(" 50000\t 100000 ")),
Some("50000 100000".to_string())
);
}
#[test]
fn env_invalid_returns_none() {
assert_eq!(cpu_max_to_write(None, Some("not-a-number")), None);
assert_eq!(cpu_max_to_write(None, Some("0 100000")), None);
assert_eq!(cpu_max_to_write(None, Some("50000 0")), None);
assert_eq!(cpu_max_to_write(None, Some("50000 100000 200000")), None);
assert_eq!(cpu_max_to_write(None, Some("-50000")), None);
}
#[test]
fn env_empty_treated_as_unset() {
assert_eq!(cpu_max_to_write(None, Some("")), None);
assert_eq!(cpu_max_to_write(None, Some(" ")), None);
assert_eq!(cpu_max_to_write(None, None), None);
}
#[test]
fn env_validation_error_diagnostic() {
assert!(cpu_max_env_validation_error(Some("not-a-number")).is_some());
assert!(cpu_max_env_validation_error(Some("0")).is_some());
assert!(cpu_max_env_validation_error(Some("50000 100000")).is_none());
assert!(cpu_max_env_validation_error(Some("max")).is_none());
assert!(cpu_max_env_validation_error(None).is_none());
assert!(cpu_max_env_validation_error(Some("")).is_none());
}
#[test]
fn spec_overrides_env_when_both_present() {
let l = limits_with_cpu(10_000, Some(50_000));
assert_eq!(
cpu_max_to_write(Some(&l), Some("max 200000")),
Some("10000 50000".to_string())
);
}
}