atagen

roundabout

public
Star0
Watch0
9117440444eb609ecea8476b5a62b03917077591
1
ssh://brewery@pls.lobotomise.me:33/atagen/roundabout.git

Blame: src/systemd.rs

caac091f257aatagen2026-10-04 11:41 UTC1
use std::{
caac091f257aatagen2026-10-04 11:41 UTC2
    env, io,
caac091f257aatagen2026-10-04 11:41 UTC3
    os::fd::{AsRawFd, FromRawFd, OwnedFd},
caac091f257aatagen2026-10-04 11:41 UTC4
    os::unix::process::CommandExt,
caac091f257aatagen2026-10-04 11:41 UTC5
    process::{Child, Command},
caac091f257aatagen2026-10-04 11:41 UTC6
    time::Duration,
caac091f257aatagen2026-10-04 11:41 UTC7
};
caac091f257aatagen2026-10-04 11:41 UTC8
caac091f257aatagen2026-10-04 11:41 UTC9
use futures_lite::StreamExt;
caac091f257aatagen2026-10-04 11:41 UTC10
use zbus::{
caac091f257aatagen2026-10-04 11:41 UTC11
    blocking::{Connection, Proxy},
caac091f257aatagen2026-10-04 11:41 UTC12
    proxy::SignalStream,
caac091f257aatagen2026-10-04 11:41 UTC13
    zvariant::{OwnedObjectPath, Value},
caac091f257aatagen2026-10-04 11:41 UTC14
};
caac091f257aatagen2026-10-04 11:41 UTC15
caac091f257aatagen2026-10-04 11:41 UTC16
use crate::{
caac091f257aatagen2026-10-04 11:41 UTC17
    config::{Overrun, RamOverrun},
caac091f257aatagen2026-10-04 11:41 UTC18
    units::Bytes,
caac091f257aatagen2026-10-04 11:41 UTC19
};
caac091f257aatagen2026-10-04 11:41 UTC20
caac091f257aatagen2026-10-04 11:41 UTC21
const EXEC: &str = "__roundabout_exec";
caac091f257aatagen2026-10-04 11:41 UTC22
const MANAGER: &str = "org.freedesktop.systemd1.Manager";
caac091f257aatagen2026-10-04 11:41 UTC23
const BUS_NAME: &str = "org.freedesktop.systemd1";
caac091f257aatagen2026-10-04 11:41 UTC24
const BUS_PATH: &str = "/org/freedesktop/systemd1";
caac091f257aatagen2026-10-04 11:41 UTC25
caac091f257aatagen2026-10-04 11:41 UTC26
pub struct Scope {
caac091f257aatagen2026-10-04 11:41 UTC27
    connection: Connection,
caac091f257aatagen2026-10-04 11:41 UTC28
    name: String,
caac091f257aatagen2026-10-04 11:41 UTC29
}
caac091f257aatagen2026-10-04 11:41 UTC30
caac091f257aatagen2026-10-04 11:41 UTC31
fn properties(
caac091f257aatagen2026-10-04 11:41 UTC32
    pid: u32,
caac091f257aatagen2026-10-04 11:41 UTC33
    cores: u32,
caac091f257aatagen2026-10-04 11:41 UTC34
    ram: Bytes,
caac091f257aatagen2026-10-04 11:41 UTC35
    overrun: &Overrun,
caac091f257aatagen2026-10-04 11:41 UTC36
) -> zbus::Result<Vec<(&'static str, Value<'static>)>> {
caac091f257aatagen2026-10-04 11:41 UTC37
    let max = ram.0 as f64 * overrun.ram_slack.max(1.0);
caac091f257aatagen2026-10-04 11:41 UTC38
    if !overrun.ram_slack.is_finite() || max >= u64::MAX as f64 {
caac091f257aatagen2026-10-04 11:41 UTC39
        return Err(zbus::Error::Failure("RAM limit must be finite".into()));
caac091f257aatagen2026-10-04 11:41 UTC40
    }
caac091f257aatagen2026-10-04 11:41 UTC41
    let mut properties = vec![
caac091f257aatagen2026-10-04 11:41 UTC42
        ("Description", Value::from("roundabout build")),
caac091f257aatagen2026-10-04 11:41 UTC43
        ("PIDs", Value::from(vec![pid])),
caac091f257aatagen2026-10-04 11:41 UTC44
        ("CollectMode", Value::from("inactive-or-failed")),
caac091f257aatagen2026-10-04 11:41 UTC45
        ("CPUAccounting", Value::from(true)),
caac091f257aatagen2026-10-04 11:41 UTC46
        ("MemoryAccounting", Value::from(true)),
caac091f257aatagen2026-10-04 11:41 UTC47
        (
caac091f257aatagen2026-10-04 11:41 UTC48
            "CPUQuotaPerSecUSec",
caac091f257aatagen2026-10-04 11:41 UTC49
            Value::from(u64::from(cores.max(1)) * 1_000_000),
caac091f257aatagen2026-10-04 11:41 UTC50
        ),
caac091f257aatagen2026-10-04 11:41 UTC51
        ("MemoryMax", Value::from((max as u64).max(1))),
caac091f257aatagen2026-10-04 11:41 UTC52
        ("OOMPolicy", Value::from("kill")),
caac091f257aatagen2026-10-04 11:41 UTC53
        ("TimeoutStopUSec", Value::from(10_000_000_u64)),
caac091f257aatagen2026-10-04 11:41 UTC54
    ];
caac091f257aatagen2026-10-04 11:41 UTC55
    if overrun.ram == RamOverrun::Throttle {
caac091f257aatagen2026-10-04 11:41 UTC56
        properties.push(("MemoryHigh", Value::from(ram.0.max(1))));
caac091f257aatagen2026-10-04 11:41 UTC57
    }
caac091f257aatagen2026-10-04 11:41 UTC58
    Ok(properties)
caac091f257aatagen2026-10-04 11:41 UTC59
}
caac091f257aatagen2026-10-04 11:41 UTC60
caac091f257aatagen2026-10-04 11:41 UTC61
fn wait_job(mut signals: SignalStream<'_>, job: &OwnedObjectPath) -> zbus::Result<()> {
caac091f257aatagen2026-10-04 11:41 UTC62
    async_io::block_on(futures_lite::future::race(
caac091f257aatagen2026-10-04 11:41 UTC63
        async {
caac091f257aatagen2026-10-04 11:41 UTC64
            while let Some(signal) = signals.next().await {
caac091f257aatagen2026-10-04 11:41 UTC65
                let (_, path, _, result): (u32, OwnedObjectPath, String, String) =
caac091f257aatagen2026-10-04 11:41 UTC66
                    signal.body().deserialize()?;
caac091f257aatagen2026-10-04 11:41 UTC67
                if path == *job {
caac091f257aatagen2026-10-04 11:41 UTC68
                    return if result == "done" {
caac091f257aatagen2026-10-04 11:41 UTC69
                        Ok(())
caac091f257aatagen2026-10-04 11:41 UTC70
                    } else {
caac091f257aatagen2026-10-04 11:41 UTC71
                        Err(zbus::Error::Failure(format!("systemd job {result}")))
caac091f257aatagen2026-10-04 11:41 UTC72
                    };
caac091f257aatagen2026-10-04 11:41 UTC73
                }
caac091f257aatagen2026-10-04 11:41 UTC74
            }
caac091f257aatagen2026-10-04 11:41 UTC75
            Err(zbus::Error::Failure(
caac091f257aatagen2026-10-04 11:41 UTC76
                "systemd disconnected before the job finished".into(),
caac091f257aatagen2026-10-04 11:41 UTC77
            ))
caac091f257aatagen2026-10-04 11:41 UTC78
        },
caac091f257aatagen2026-10-04 11:41 UTC79
        async {
caac091f257aatagen2026-10-04 11:41 UTC80
            async_io::Timer::after(Duration::from_secs(15)).await;
caac091f257aatagen2026-10-04 11:41 UTC81
            Err(zbus::Error::Failure("systemd job timed out".into()))
caac091f257aatagen2026-10-04 11:41 UTC82
        },
caac091f257aatagen2026-10-04 11:41 UTC83
    ))
caac091f257aatagen2026-10-04 11:41 UTC84
}
caac091f257aatagen2026-10-04 11:41 UTC85
caac091f257aatagen2026-10-04 11:41 UTC86
impl Scope {
caac091f257aatagen2026-10-04 11:41 UTC87
    fn manager(&self) -> zbus::Result<Proxy<'_>> {
caac091f257aatagen2026-10-04 11:41 UTC88
        Proxy::new(&self.connection, BUS_NAME, BUS_PATH, MANAGER)
caac091f257aatagen2026-10-04 11:41 UTC89
    }
caac091f257aatagen2026-10-04 11:41 UTC90
caac091f257aatagen2026-10-04 11:41 UTC91
    fn start(
caac091f257aatagen2026-10-04 11:41 UTC92
        connection: Connection,
caac091f257aatagen2026-10-04 11:41 UTC93
        pid: u32,
caac091f257aatagen2026-10-04 11:41 UTC94
        lease: u64,
caac091f257aatagen2026-10-04 11:41 UTC95
        cores: u32,
caac091f257aatagen2026-10-04 11:41 UTC96
        ram: Bytes,
caac091f257aatagen2026-10-04 11:41 UTC97
        overrun: &Overrun,
caac091f257aatagen2026-10-04 11:41 UTC98
    ) -> zbus::Result<Self> {
caac091f257aatagen2026-10-04 11:41 UTC99
        let scope = Self {
caac091f257aatagen2026-10-04 11:41 UTC100
            connection,
caac091f257aatagen2026-10-04 11:41 UTC101
            name: format!("roundabout-{}-{lease}-{pid}.scope", std::process::id()),
caac091f257aatagen2026-10-04 11:41 UTC102
        };
caac091f257aatagen2026-10-04 11:41 UTC103
        let manager = scope.manager()?;
caac091f257aatagen2026-10-04 11:41 UTC104
        let signals = async_io::block_on(
caac091f257aatagen2026-10-04 11:41 UTC105
            manager
caac091f257aatagen2026-10-04 11:41 UTC106
                .inner()
caac091f257aatagen2026-10-04 11:41 UTC107
                .receive_signal_with_args("JobRemoved", &[(2, &scope.name)]),
caac091f257aatagen2026-10-04 11:41 UTC108
        )?;
caac091f257aatagen2026-10-04 11:41 UTC109
        let _: () = manager.call("Subscribe", &())?;
caac091f257aatagen2026-10-04 11:41 UTC110
        let auxiliary: Vec<(&str, Vec<(&str, Value<'_>)>)> = Vec::new();
caac091f257aatagen2026-10-04 11:41 UTC111
        let job: OwnedObjectPath = manager.call(
caac091f257aatagen2026-10-04 11:41 UTC112
            "StartTransientUnit",
caac091f257aatagen2026-10-04 11:41 UTC113
            &(
caac091f257aatagen2026-10-04 11:41 UTC114
                &scope.name,
caac091f257aatagen2026-10-04 11:41 UTC115
                "fail",
caac091f257aatagen2026-10-04 11:41 UTC116
                properties(pid, cores, ram, overrun)?,
caac091f257aatagen2026-10-04 11:41 UTC117
                auxiliary,
caac091f257aatagen2026-10-04 11:41 UTC118
            ),
caac091f257aatagen2026-10-04 11:41 UTC119
        )?;
caac091f257aatagen2026-10-04 11:41 UTC120
        wait_job(signals, &job)?;
caac091f257aatagen2026-10-04 11:41 UTC121
        drop(manager);
caac091f257aatagen2026-10-04 11:41 UTC122
        Ok(scope)
caac091f257aatagen2026-10-04 11:41 UTC123
    }
caac091f257aatagen2026-10-04 11:41 UTC124
caac091f257aatagen2026-10-04 11:41 UTC125
    pub fn signal(&self, signal: i32) -> zbus::Result<()> {
caac091f257aatagen2026-10-04 11:41 UTC126
        self.manager()?
caac091f257aatagen2026-10-04 11:41 UTC127
            .call("KillUnit", &(&self.name, "all", signal))
caac091f257aatagen2026-10-04 11:41 UTC128
    }
caac091f257aatagen2026-10-04 11:41 UTC129
caac091f257aatagen2026-10-04 11:41 UTC130
    fn stop(&self) -> zbus::Result<()> {
caac091f257aatagen2026-10-04 11:41 UTC131
        let manager = self.manager()?;
caac091f257aatagen2026-10-04 11:41 UTC132
        let signals = async_io::block_on(
caac091f257aatagen2026-10-04 11:41 UTC133
            manager
caac091f257aatagen2026-10-04 11:41 UTC134
                .inner()
caac091f257aatagen2026-10-04 11:41 UTC135
                .receive_signal_with_args("JobRemoved", &[(2, &self.name)]),
caac091f257aatagen2026-10-04 11:41 UTC136
        )?;
caac091f257aatagen2026-10-04 11:41 UTC137
        let job: OwnedObjectPath = manager.call("StopUnit", &(&self.name, "replace"))?;
caac091f257aatagen2026-10-04 11:41 UTC138
        wait_job(signals, &job)
caac091f257aatagen2026-10-04 11:41 UTC139
    }
caac091f257aatagen2026-10-04 11:41 UTC140
}
caac091f257aatagen2026-10-04 11:41 UTC141
caac091f257aatagen2026-10-04 11:41 UTC142
impl Drop for Scope {
caac091f257aatagen2026-10-04 11:41 UTC143
    fn drop(&mut self) {
caac091f257aatagen2026-10-04 11:41 UTC144
        let _ = self.stop();
caac091f257aatagen2026-10-04 11:41 UTC145
    }
caac091f257aatagen2026-10-04 11:41 UTC146
}
caac091f257aatagen2026-10-04 11:41 UTC147
caac091f257aatagen2026-10-04 11:41 UTC148
fn gate() -> io::Result<(OwnedFd, OwnedFd)> {
caac091f257aatagen2026-10-04 11:41 UTC149
    let mut descriptors = [-1; 2];
caac091f257aatagen2026-10-04 11:41 UTC150
    if unsafe { libc::pipe2(descriptors.as_mut_ptr(), libc::O_CLOEXEC) } != 0 {
caac091f257aatagen2026-10-04 11:41 UTC151
        return Err(io::Error::last_os_error());
caac091f257aatagen2026-10-04 11:41 UTC152
    }
caac091f257aatagen2026-10-04 11:41 UTC153
    Ok(unsafe {
caac091f257aatagen2026-10-04 11:41 UTC154
        (
caac091f257aatagen2026-10-04 11:41 UTC155
            OwnedFd::from_raw_fd(descriptors[0]),
caac091f257aatagen2026-10-04 11:41 UTC156
            OwnedFd::from_raw_fd(descriptors[1]),
caac091f257aatagen2026-10-04 11:41 UTC157
        )
caac091f257aatagen2026-10-04 11:41 UTC158
    })
caac091f257aatagen2026-10-04 11:41 UTC159
}
caac091f257aatagen2026-10-04 11:41 UTC160
caac091f257aatagen2026-10-04 11:41 UTC161
pub fn spawn(
caac091f257aatagen2026-10-04 11:41 UTC162
    command: &Command,
caac091f257aatagen2026-10-04 11:41 UTC163
    lease: u64,
caac091f257aatagen2026-10-04 11:41 UTC164
    cores: u32,
caac091f257aatagen2026-10-04 11:41 UTC165
    ram: Bytes,
caac091f257aatagen2026-10-04 11:41 UTC166
    overrun: &Overrun,
caac091f257aatagen2026-10-04 11:41 UTC167
) -> Result<(Child, Scope), String> {
caac091f257aatagen2026-10-04 11:41 UTC168
    let connection =
caac091f257aatagen2026-10-04 11:41 UTC169
        Connection::session().map_err(|error| format!("can't connect to the user bus, {error}"))?;
caac091f257aatagen2026-10-04 11:41 UTC170
    let (read, write) = gate().map_err(|error| error.to_string())?;
caac091f257aatagen2026-10-04 11:41 UTC171
    let descriptor = read.as_raw_fd();
caac091f257aatagen2026-10-04 11:41 UTC172
    let mut runner = Command::new(env::current_exe().map_err(|error| error.to_string())?);
caac091f257aatagen2026-10-04 11:41 UTC173
    runner
caac091f257aatagen2026-10-04 11:41 UTC174
        .arg(EXEC)
caac091f257aatagen2026-10-04 11:41 UTC175
        .arg(descriptor.to_string())
caac091f257aatagen2026-10-04 11:41 UTC176
        .arg(command.get_program())
caac091f257aatagen2026-10-04 11:41 UTC177
        .args(command.get_args())
caac091f257aatagen2026-10-04 11:41 UTC178
        .process_group(0);
caac091f257aatagen2026-10-04 11:41 UTC179
    for (name, value) in command.get_envs() {
caac091f257aatagen2026-10-04 11:41 UTC180
        if let Some(value) = value {
caac091f257aatagen2026-10-04 11:41 UTC181
            runner.env(name, value);
caac091f257aatagen2026-10-04 11:41 UTC182
        } else {
caac091f257aatagen2026-10-04 11:41 UTC183
            runner.env_remove(name);
caac091f257aatagen2026-10-04 11:41 UTC184
        }
caac091f257aatagen2026-10-04 11:41 UTC185
    }
caac091f257aatagen2026-10-04 11:41 UTC186
    let parent = unsafe { libc::getpid() };
caac091f257aatagen2026-10-04 11:41 UTC187
    unsafe {
caac091f257aatagen2026-10-04 11:41 UTC188
        runner.pre_exec(move || {
caac091f257aatagen2026-10-04 11:41 UTC189
            if libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGTERM) != 0
caac091f257aatagen2026-10-04 11:41 UTC190
                || libc::fcntl(descriptor, libc::F_SETFD, 0) != 0
caac091f257aatagen2026-10-04 11:41 UTC191
            {
caac091f257aatagen2026-10-04 11:41 UTC192
                return Err(io::Error::last_os_error());
caac091f257aatagen2026-10-04 11:41 UTC193
            }
caac091f257aatagen2026-10-04 11:41 UTC194
            if libc::getppid() != parent {
caac091f257aatagen2026-10-04 11:41 UTC195
                return Err(io::Error::other("parent exited before launch"));
caac091f257aatagen2026-10-04 11:41 UTC196
            }
caac091f257aatagen2026-10-04 11:41 UTC197
            Ok(())
caac091f257aatagen2026-10-04 11:41 UTC198
        });
caac091f257aatagen2026-10-04 11:41 UTC199
    }
caac091f257aatagen2026-10-04 11:41 UTC200
    let mut child = runner.spawn().map_err(|error| error.to_string())?;
caac091f257aatagen2026-10-04 11:41 UTC201
    drop(read);
caac091f257aatagen2026-10-04 11:41 UTC202
    let result = Scope::start(connection, child.id(), lease, cores, ram, overrun)
caac091f257aatagen2026-10-04 11:41 UTC203
        .map_err(|error| format!("can't start the transient scope, {error}"))
caac091f257aatagen2026-10-04 11:41 UTC204
        .and_then(|scope| {
caac091f257aatagen2026-10-04 11:41 UTC205
            if unsafe { libc::write(write.as_raw_fd(), b"1".as_ptr().cast(), 1) } != 1 {
caac091f257aatagen2026-10-04 11:41 UTC206
                return Err(io::Error::last_os_error().to_string());
caac091f257aatagen2026-10-04 11:41 UTC207
            }
caac091f257aatagen2026-10-04 11:41 UTC208
            Ok(scope)
caac091f257aatagen2026-10-04 11:41 UTC209
        });
caac091f257aatagen2026-10-04 11:41 UTC210
    drop(write);
caac091f257aatagen2026-10-04 11:41 UTC211
    match result {
caac091f257aatagen2026-10-04 11:41 UTC212
        Ok(scope) => Ok((child, scope)),
caac091f257aatagen2026-10-04 11:41 UTC213
        Err(error) => {
caac091f257aatagen2026-10-04 11:41 UTC214
            let _ = child.kill();
caac091f257aatagen2026-10-04 11:41 UTC215
            let _ = child.wait();
caac091f257aatagen2026-10-04 11:41 UTC216
            Err(error)
caac091f257aatagen2026-10-04 11:41 UTC217
        }
caac091f257aatagen2026-10-04 11:41 UTC218
    }
caac091f257aatagen2026-10-04 11:41 UTC219
}
caac091f257aatagen2026-10-04 11:41 UTC220
caac091f257aatagen2026-10-04 11:41 UTC221
pub fn exec() -> Option<i32> {
caac091f257aatagen2026-10-04 11:41 UTC222
    let mut arguments = env::args_os().skip(1);
caac091f257aatagen2026-10-04 11:41 UTC223
    if arguments.next()?.as_encoded_bytes() != EXEC.as_bytes() {
caac091f257aatagen2026-10-04 11:41 UTC224
        return None;
caac091f257aatagen2026-10-04 11:41 UTC225
    }
caac091f257aatagen2026-10-04 11:41 UTC226
    let Some(descriptor) = arguments
caac091f257aatagen2026-10-04 11:41 UTC227
        .next()
caac091f257aatagen2026-10-04 11:41 UTC228
        .and_then(|arg| arg.to_str()?.parse::<i32>().ok())
caac091f257aatagen2026-10-04 11:41 UTC229
        .filter(|fd| *fd >= 3)
caac091f257aatagen2026-10-04 11:41 UTC230
    else {
caac091f257aatagen2026-10-04 11:41 UTC231
        return Some(1);
caac091f257aatagen2026-10-04 11:41 UTC232
    };
caac091f257aatagen2026-10-04 11:41 UTC233
    let descriptor = unsafe { OwnedFd::from_raw_fd(descriptor) };
caac091f257aatagen2026-10-04 11:41 UTC234
    let mut byte = [0_u8];
caac091f257aatagen2026-10-04 11:41 UTC235
    loop {
caac091f257aatagen2026-10-04 11:41 UTC236
        let count = unsafe { libc::read(descriptor.as_raw_fd(), byte.as_mut_ptr().cast(), 1) };
caac091f257aatagen2026-10-04 11:41 UTC237
        if count == 1 && byte[0] == b'1' {
caac091f257aatagen2026-10-04 11:41 UTC238
            break;
caac091f257aatagen2026-10-04 11:41 UTC239
        }
caac091f257aatagen2026-10-04 11:41 UTC240
        if count != -1 || io::Error::last_os_error().kind() != io::ErrorKind::Interrupted {
caac091f257aatagen2026-10-04 11:41 UTC241
            return Some(1);
caac091f257aatagen2026-10-04 11:41 UTC242
        }
caac091f257aatagen2026-10-04 11:41 UTC243
    }
caac091f257aatagen2026-10-04 11:41 UTC244
    drop(descriptor);
caac091f257aatagen2026-10-04 11:41 UTC245
    let Some(program) = arguments.next() else {
caac091f257aatagen2026-10-04 11:41 UTC246
        return Some(1);
caac091f257aatagen2026-10-04 11:41 UTC247
    };
caac091f257aatagen2026-10-04 11:41 UTC248
    let error = Command::new(&program).args(arguments).exec();
caac091f257aatagen2026-10-04 11:41 UTC249
    eprintln!(
fdad58f8133aatagen2026-10-25 23:14 UTC250
        "roundabout: couldn't start {}: {error}",
caac091f257aatagen2026-10-04 11:41 UTC251
        program.to_string_lossy()
caac091f257aatagen2026-10-04 11:41 UTC252
    );
caac091f257aatagen2026-10-04 11:41 UTC253
    Some(127)
caac091f257aatagen2026-10-04 11:41 UTC254
}
caac091f257aatagen2026-10-04 11:41 UTC255
caac091f257aatagen2026-10-04 11:41 UTC256
#[cfg(test)]
caac091f257aatagen2026-10-04 11:41 UTC257
mod tests {
caac091f257aatagen2026-10-04 11:41 UTC258
    use super::*;
caac091f257aatagen2026-10-04 11:41 UTC259
caac091f257aatagen2026-10-04 11:41 UTC260
    #[test]
caac091f257aatagen2026-10-04 11:41 UTC261
    fn limits_follow_the_grant_with_memory_headroom() {
caac091f257aatagen2026-10-04 11:41 UTC262
        let overrun = Overrun::default();
caac091f257aatagen2026-10-04 11:41 UTC263
        let properties = properties(123, 8, Bytes::gib(4), &overrun).unwrap();
caac091f257aatagen2026-10-04 11:41 UTC264
        assert!(properties.contains(&("PIDs", Value::from(vec![123_u32]))));
caac091f257aatagen2026-10-04 11:41 UTC265
        assert!(properties.contains(&("CPUQuotaPerSecUSec", Value::from(8_000_000_u64))));
caac091f257aatagen2026-10-04 11:41 UTC266
        assert!(properties.contains(&("MemoryMax", Value::from(Bytes::gib(5).0))));
caac091f257aatagen2026-10-04 11:41 UTC267
        assert!(!properties.iter().any(|(name, _)| *name == "MemoryHigh"));
caac091f257aatagen2026-10-04 11:41 UTC268
    }
caac091f257aatagen2026-10-04 11:41 UTC269
caac091f257aatagen2026-10-04 11:41 UTC270
    #[test]
caac091f257aatagen2026-10-04 11:41 UTC271
    fn throttle_starts_at_the_granted_memory() {
caac091f257aatagen2026-10-04 11:41 UTC272
        let overrun = Overrun {
caac091f257aatagen2026-10-04 11:41 UTC273
            ram: RamOverrun::Throttle,
caac091f257aatagen2026-10-04 11:41 UTC274
            ..Overrun::default()
caac091f257aatagen2026-10-04 11:41 UTC275
        };
caac091f257aatagen2026-10-04 11:41 UTC276
        let properties = properties(123, 2, Bytes::gib(2), &overrun).unwrap();
caac091f257aatagen2026-10-04 11:41 UTC277
        assert!(properties.contains(&("MemoryHigh", Value::from(Bytes::gib(2).0))));
caac091f257aatagen2026-10-04 11:41 UTC278
        assert!(properties.contains(&("MemoryMax", Value::from(Bytes::mib(2560).0))));
caac091f257aatagen2026-10-04 11:41 UTC279
    }
caac091f257aatagen2026-10-04 11:41 UTC280
caac091f257aatagen2026-10-04 11:41 UTC281
    #[test]
caac091f257aatagen2026-10-04 11:41 UTC282
    fn small_grants_never_become_unlimited() {
caac091f257aatagen2026-10-04 11:41 UTC283
        let overrun = Overrun {
caac091f257aatagen2026-10-04 11:41 UTC284
            ram_slack: 0.5,
caac091f257aatagen2026-10-04 11:41 UTC285
            ..Overrun::default()
caac091f257aatagen2026-10-04 11:41 UTC286
        };
caac091f257aatagen2026-10-04 11:41 UTC287
        let properties = properties(123, 0, Bytes::ZERO, &overrun).unwrap();
caac091f257aatagen2026-10-04 11:41 UTC288
        assert!(properties.contains(&("CPUQuotaPerSecUSec", Value::from(1_000_000_u64))));
caac091f257aatagen2026-10-04 11:41 UTC289
        assert!(properties.contains(&("MemoryMax", Value::from(1_u64))));
caac091f257aatagen2026-10-04 11:41 UTC290
    }
caac091f257aatagen2026-10-04 11:41 UTC291
caac091f257aatagen2026-10-04 11:41 UTC292
    #[test]
caac091f257aatagen2026-10-04 11:41 UTC293
    fn nonfinite_limits_are_rejected() {
caac091f257aatagen2026-10-04 11:41 UTC294
        for ram_slack in [f64::INFINITY, f64::NEG_INFINITY, f64::NAN, f64::MAX] {
caac091f257aatagen2026-10-04 11:41 UTC295
            let overrun = Overrun {
caac091f257aatagen2026-10-04 11:41 UTC296
                ram_slack,
caac091f257aatagen2026-10-04 11:41 UTC297
                ..Overrun::default()
caac091f257aatagen2026-10-04 11:41 UTC298
            };
caac091f257aatagen2026-10-04 11:41 UTC299
            assert!(properties(123, 1, Bytes::gib(2), &overrun).is_err());
caac091f257aatagen2026-10-04 11:41 UTC300
        }
caac091f257aatagen2026-10-04 11:41 UTC301
    }
caac091f257aatagen2026-10-04 11:41 UTC302
}