Backdrop

Struct Backdrop 

Source
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

Source

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.

Source

pub fn push_cgroup(self, def: CgroupDef) -> Self

See Self::cgroups for the ordering guarantee and the cell_id allocation example.

Source

pub fn extend_cgroups<I: IntoIterator<Item = CgroupDef>>(self, defs: I) -> Self

See Self::cgroups for the ordering guarantee.

Source

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.

Source

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.

Source

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.

Source

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).

Source

pub fn extend_ops<I: IntoIterator<Item = Op>>(self, ops: I) -> Self

See Self::ops for run order.

Source

pub fn is_empty(&self) -> bool

True when the Backdrop has no persistent entities declared. execute_scenario checks this to skip the Backdrop setup phase entirely — zero overhead for scenarios that do not use persistent state.

Trait Implementations§

Source§

impl Clone for Backdrop

Source§

fn clone(&self) -> Backdrop

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Backdrop

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Backdrop

Source§

fn default() -> Backdrop

Returns the “default value” for a type. Read more
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.

Source§

fn from_iter<I: IntoIterator<Item = CgroupDef>>(defs: I) -> Self

Creates a value from an iterator. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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
§

impl<T> Pointable for T

§

const ALIGN: usize

The alignment of pointer.
§

type Init = T

The type for initializers.
§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
§

impl<T> PolicyExt for T
where T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

impl<T> MaybeSend for T
where T: Send,

§

impl<T> MaybeSend for T
where T: Send,