Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

CgroupManager

CgroupManager manages cgroup v2 filesystem operations. It creates, configures, and removes cgroups under a parent directory.

use ktstr::prelude::*;

pub struct CgroupManager {
    parent: PathBuf,
}

Construction

use std::collections::BTreeSet;

let cgroups = CgroupManager::new("/sys/fs/cgroup/ktstr");
let mut controllers = BTreeSet::new();
controllers.insert(Controller::Cpuset);
controllers.insert(Controller::Cpu);
cgroups.setup(&controllers)?; // create parent dir, enable cpuset + cpu controllers

new() sets the parent path. setup() takes a &BTreeSet<Controller> (variants: Cpuset, Cpu, Memory, Pids, Io), creates the parent directory if it does not exist, and enables the requested controllers on every ancestor from /sys/fs/cgroup down to the parent by writing to each level’s cgroup.subtree_control. An empty set creates the directory and returns without touching subtree_control. The deterministic BTreeSet iteration order keeps the rendered subtree_control write stable between runs.

Methods

parent_path() -> &Path – returns the parent cgroup directory path.

create_cgroup(name) – creates a child cgroup directory. Idempotent: no error if the directory already exists. Supports nested paths (e.g. "nested/deep"). For nested paths, enables +cpuset on intermediate cgroups’ subtree_control.

remove_cgroup(name) – drains tasks from the child cgroup to the cgroup filesystem root, then removes the directory. No error if the cgroup does not exist.

set_cpuset(name, cpus) – writes cpuset.cpus for a child cgroup. The BTreeSet<usize> is formatted as a compact range string via TestTopology::cpuset_string() (e.g. "0-3,5,7-9").

clear_cpuset(name) – writes an empty string to cpuset.cpus, which inherits the parent’s cpuset.

move_task(name, pid) – writes a single PID to the child cgroup’s cgroup.procs.

move_tasks(name, pids) – moves all PIDs from a slice into the child cgroup. Tolerates ESRCH (task exited between listing and migration) with a warning. Retries EBUSY up to 3 times with 100ms backoff for transient rejections from sched_ext BPF cgroup_prep_move callbacks. Propagates EBUSY after retries exhausted. Propagates all other errors immediately.

drain_tasks(name) – moves all tasks from a child cgroup to the cgroup filesystem root (/sys/fs/cgroup) by reading cgroup.procs and writing each PID to the root’s cgroup.procs. Drains to root because the parent has subtree_control set and the kernel’s no-internal-process constraint rejects writes to a cgroup with active controllers.

cleanup_all() – recursively removes all child cgroups under the parent (depth-first), draining tasks at each level. Keeps the parent directory itself.

Timeout protection

All cgroup filesystem writes use a 2-second timeout. The write runs in a spawned thread; if it does not complete within the timeout, the caller gets an error. This prevents test hangs when cgroup operations block in the kernel (e.g. during scheduler reconfigurations).

Usage in scenarios

Scenarios access CgroupManager through Ctx.cgroups. The typical pattern is:

fn custom_scenario(ctx: &Ctx) -> Result<AssertResult> {
    let mut guard = CgroupGroup::new(ctx.cgroups);
    guard.add_cgroup("cg_0", &cpuset)?;

    let mut h = WorkloadHandle::spawn(&config)?;
    ctx.cgroups.move_tasks("cg_0", &h.worker_pids_for_cgroup_procs()?)?;
    h.start(); // workers block until start() is called

    // ... run workload ...

    // `guard` drops at end of scope and removes cg_0 even on error.
    Ok(result)
}

Bypass CgroupGroup only when you need to hand the cgroup’s lifetime to a different owner; the RAII wrapper is the default because it removes the cgroup on every error path, not just the happy path.

See also: CgroupGroup for RAII cleanup, WorkloadHandle for worker lifecycle, TestTopology for cpuset generation.