CgroupGroup
CgroupGroup is an RAII guard that removes cgroups on drop. It
prevents cgroup leaks when workload spawning or other operations fail
between cgroup creation and cleanup.
use ktstr::prelude::*;
#[must_use = "dropping a CgroupGroup immediately destroys the cgroups it manages"]
pub struct CgroupGroup<'a> {
cgroups: &'a dyn CgroupOps,
names: Vec<String>,
}
Methods
new(cgroups: &dyn CgroupOps) -> Self – creates an empty group
bound to any implementor of CgroupOps (e.g.
CgroupManager in production, an in-memory fake
in tests).
add_cgroup(name, cpuset) -> Result<()> – creates a cgroup and
sets its cpuset. The cgroup is tracked for removal on drop.
add_cgroup_no_cpuset(name) -> Result<()> – creates a cgroup
without setting a cpuset. The cgroup is tracked for removal on drop.
names() -> &[String] – returns the names of all tracked cgroups.
Drop behavior
When the CgroupGroup is dropped, it calls remove_cgroup() on each
tracked cgroup in reverse insertion order so nested children are
removed before their parents (a parent still holding child
directories would fail with ENOTEMPTY).
ENOENT is the one errno the drop swallows silently — it indicates
the directory is already gone (the post-condition cleanup owes), which
can legitimately happen via a TOCTOU race between the inner
exists() check and remove_dir. Every other error (EBUSY from a
surviving task, EACCES, a broken cgroupfs mount, etc.) is emitted
as a tracing::warn! record carrying the cgroup name, the full error
chain, and — for EBUSY or EACCES — a short remediation hint. The
drop never panics and never returns an error (it cannot), but
teardown failures are visible in logs rather than silently swallowed.
Usage
CgroupGroup is the standard pattern for cgroup lifecycle management
in custom scenarios and in run_scenario() for data-driven scenarios.
fn custom_scenario(ctx: &Ctx) -> Result<AssertResult> {
let mut guard = CgroupGroup::new(ctx.cgroups);
guard.add_cgroup("cg_0", &cpuset_a)?;
guard.add_cgroup("cg_1", &cpuset_b)?;
// If WorkloadHandle::spawn() fails here, guard drops
// and both cgroups are removed automatically.
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, removing cg_0 and cg_1.
Ok(result)
}
The helper function setup_cgroups()
returns a CgroupGroup alongside the worker handles:
let (handles, _guard) = setup_cgroups(ctx, 2, &wl)?;
// _guard lives until end of scope; cgroups are cleaned up on drop.
See also: CgroupManager for filesystem operations, WorkloadHandle for worker lifecycle, TestTopology for cpuset generation.