# CgroupManager
`CgroupManager` manages cgroup v2 filesystem operations. It creates,
configures, and removes cgroups under a parent directory.
```rust,ignore
use ktstr::prelude::*;
pub struct CgroupManager {
parent: PathBuf,
}
```
## Construction
```rust,ignore
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:
```rust,ignore
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`](cgroup-group.md) 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](cgroup-group.md) for RAII cleanup,
[WorkloadHandle](workload-handle.md) for worker lifecycle,
[TestTopology](../concepts/topology.md) for cpuset generation.