pub struct Backdrop {
pub cgroups: Vec<CgroupDef>,
pub payloads: Vec<&'static Payload>,
pub ops: Vec<Op>,
}Expand description
Persistent state for a Step sequence.
Hold long-running entities here instead of re-declaring them in
every Step. execute_scenario
owns the Backdrop for the duration of the run, sets up every
declared entity once before the first Step, and tears them down
at the end (success or Err).
§Empty default
Scenarios with no persistent state pass Backdrop::new(),
which is also what the shorthand
execute_steps /
execute_defs wrappers forward to
internally. There is no cost to using the empty default — the
runtime skips the Backdrop setup phase entirely when every vec
is empty.
§Clone and cgroup-name collisions
Backdrop derives Clone, so a test can copy a base Backdrop
and attach the copies to different scenarios. Do not pass a
cloned Backdrop into a sibling scenario in the same process /
VM without rewriting the cgroup names first. Every cgroup in
cgroups is created at the same path
(/sys/fs/cgroup/<parent>/<name>); two scenarios both calling
setup on a Backdrop with the same names silently share the
cgroup’s tasks and counters — the second setup finds the path
already exists, skips mkdir, and attaches its workers alongside
the first scenario’s. No EEXIST surfaces (the kernel-level
mkdir(2) race is absorbed by std::fs::create_dir_all), so
diagnose by unexpected cgroup.procs task counts or doubled
metric counters rather than a returned error. A typical safe
shape is
base.clone().rename_cgroups(|n| format!("{n}_{idx}")) (caller-
provided helper) before attaching to scenario idx. The clone
derive is provided for builder-style composition (forking a
base, then conditionally appending entries) where the resulting
Backdrop is attached to ONE scenario — sibling-scenario use
requires the rename pass.
§Example
use ktstr::prelude::*;
#[derive(Payload)]
#[payload(binary = "stress-ng")]
#[default_args("--cpu", "2")]
struct BgLoadPayload;
// Worker-bearing cgroup + empty move target + long-running payload,
// all persistent for the scenario.
let backdrop = Backdrop::new()
.push_cgroup(CgroupDef::named("bg_cell").cpuset(CpusetSpec::disjoint(0, 2)))
.push_op(Op::add_cgroup("bg_overflow"))
.push_payload(&BG_LOAD);Fields§
§cgroups: Vec<CgroupDef>Long-lived cgroups created once and removed at scenario end.
Any Step can reference them by name via Op::MoveAllTasks,
Op::SetCpuset, etc. Every CgroupDef here spawns at
least one worker (declared WorkSpec
entries, or a single default WorkSpec when works is empty).
Declare empty move-target cgroups via Self::ops /
Self::push_op using Op::AddCgroup instead.
§Ordering guarantee
Cgroups are created in DECLARATION ORDER — the order they
appear in this Vec. The Backdrop setup phase iterates
cgroups front-to-back and runs each CgroupDef’s setup
(mkdir, cpuset/sysfs writes, worker spawn) one at a time.
push_cgroup(a).push_cgroup(b) creates a first, then b.
This matters for any scheduler whose internal IDs are
assigned in cgroup-creation order — scx_mitosis, for
example, allocates cell_ids monotonically on cgroup
creation (observed via cgroup-fs inotify, not on first
task attach) and reuses freed IDs LIFO from a free-list
when prior cells were destroyed. The cgroup declared first
gets cell_id = 1, the second gets cell_id = 2, and so
on for the initial allocation sequence. A test that wants a
sparse cell_id range (e.g. remove the middle cell to leave
a gap) can rely on the framework-side declaration order:
declare cg_a, cg_b, cg_c to get cell_id = 1, 2, 3,
then Op::RemoveCgroup("cg_b") at a Step boundary leaves
cells 1 and 3 live with a cell_id = 2 hole. The next
single-cgroup allocation after that hole reuses the freed
cell_id = 2 before bumping next_cell_id further — LIFO
from the free-list, not lowest-free.
Multi-delete caveat. When several cgroups are removed
in one inotify-batched event (or two RemoveCgroup ops fire
before scx_mitosis services any of them), scx_mitosis
inserts the freed IDs into the free-list via
HashSet::iter() — hash-bucket order, NOT removal order.
Subsequent allocations still pop LIFO, but the LIFO is
against an arbitrarily-permuted insertion order, so
“remove a, b, c in this order → reuse c, b, a” does NOT
hold for multi-cell batches. Single-delete patterns
(one RemoveCgroup per Step) reuse the freed ID
deterministically.
The cell_id assignment itself is the scheduler’s responsibility, not the framework’s. The Backdrop only guarantees the cgroup-creation order; the scheduler binary observes the resulting creation order and assigns whatever internal IDs its policy dictates.
payloads: Vec<&'static Payload>Long-lived binary payloads spawned once before the first
Step. The runtime holds the live handles for the duration of
the Step sequence and drains them via .kill() (preserving
metric emission) at scenario teardown.
ops: Vec<Op>Raw Ops applied during Backdrop setup, before any Step
runs. Run AFTER Self::cgroups apply_setup and BEFORE
Self::payloads spawn, in declaration order. Backdrop
ops run with full authority — they can target Backdrop
cgroups with Op::RemoveCgroup / Op::StopCgroup /
Op::MoveAllTasks where step-local ops would be
rejected, since the Backdrop owns the cgroups it’s setting
up. Any cgroup / handle / payload these ops create is
tracked by the Backdrop slot and tears down at scenario
end. The typical use is Op::AddCgroup for empty
move-target cgroups (a CgroupDef can’t express the
zero-worker case because apply_setup forces a worker
spawn).
Implementations§
Source§impl Backdrop
impl Backdrop
Sourcepub const fn new() -> Self
pub const fn new() -> Self
Fresh empty Backdrop — no persistent state. Builder entry
point: chain Self::push_cgroup / Self::push_payload /
Self::push_op to populate.
Equivalent to Default::default, but
const fn so the value is usable in static/const
contexts (Default::default is not yet const-stable). Prefer
Backdrop::new() at construction sites; ..Default::default()
remains available inside non-const struct-update expressions.
Sourcepub fn push_cgroup(self, def: CgroupDef) -> Self
pub fn push_cgroup(self, def: CgroupDef) -> Self
See Self::cgroups for the ordering guarantee and the
cell_id allocation example.
Sourcepub fn extend_cgroups<I: IntoIterator<Item = CgroupDef>>(self, defs: I) -> Self
pub fn extend_cgroups<I: IntoIterator<Item = CgroupDef>>(self, defs: I) -> Self
See Self::cgroups for the ordering guarantee.
Sourcepub fn from_cgroups<I: IntoIterator<Item = CgroupDef>>(defs: I) -> Self
pub fn from_cgroups<I: IntoIterator<Item = CgroupDef>>(defs: I) -> Self
Construct from any CgroupDef iterator (most commonly a
Vec<CgroupDef> built by a test-side helper). Equivalent to
Backdrop::new().extend_cgroups(defs) but reads as a single
constructor at the use site; the FromIterator impl on
Backdrop supports the .collect() form for the same case.
Declaration order is preserved per Self::cgroups.
Sourcepub fn push_payload(self, payload: &'static Payload) -> Self
pub fn push_payload(self, payload: &'static Payload) -> Self
Binary-kind payload with no extra args. See Self::payloads
for lifecycle. For custom args or cgroup placement use
Self::push_op with Op::run_payload / Op::run_payload_in_cgroup.
Sourcepub fn extend_payloads<I: IntoIterator<Item = &'static Payload>>(
self,
payloads: I,
) -> Self
pub fn extend_payloads<I: IntoIterator<Item = &'static Payload>>( self, payloads: I, ) -> Self
See Self::push_payload; use Self::extend_ops with
Op::run_payload entries for per-payload args.
Sourcepub fn push_op(self, op: Op) -> Self
pub fn push_op(self, op: Op) -> Self
See Self::ops for run order. Typical use:
Op::AddCgroup for empty move-target cgroups (a
CgroupDef always spawns at least one worker).
Sourcepub fn extend_ops<I: IntoIterator<Item = Op>>(self, ops: I) -> Self
pub fn extend_ops<I: IntoIterator<Item = Op>>(self, ops: I) -> Self
See Self::ops for run order.
Trait Implementations§
Source§impl FromIterator<CgroupDef> for Backdrop
Backdrop::from_iter(cgroups) / cgroups.into_iter().collect()
shortcuts for the common “build a Backdrop from a Vec of cgroup
defs” pattern test fixtures repeat. Equivalent to
Backdrop::from_cgroups; declaration order is preserved.
impl FromIterator<CgroupDef> for Backdrop
Backdrop::from_iter(cgroups) / cgroups.into_iter().collect()
shortcuts for the common “build a Backdrop from a Vec of cgroup
defs” pattern test fixtures repeat. Equivalent to
Backdrop::from_cgroups; declaration order is preserved.
Auto Trait Implementations§
impl Freeze for Backdrop
impl RefUnwindSafe for Backdrop
impl Send for Backdrop
impl Sync for Backdrop
impl Unpin for Backdrop
impl UnwindSafe for Backdrop
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more