1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
//! e2e for `WorkerCtx`: a `Custom` worker discovers its cgroup cpuset
//! and sibling pids THROUGH the ctx (not raw `sched_getaffinity` /
//! `cgroup.procs` scraping) and uses them to churn its siblings'
//! affinity. The payoff: a custom probe gets the same
//! runtime environment the built-in `WorkType::CrossAffinityChurn`
//! computes, without re-rolling the syscalls.
use ktstr::prelude::*;
use std::sync::atomic::Ordering;
const KTSTR_SCHED: Scheduler =
Scheduler::named("ktstr_sched").binary(SchedulerSpec::Discover("scx-ktstr"));
// A cross-task affinity flipper built ENTIRELY on `WorkerCtx`: `cpus()`
// supplies the cpuset to derive two one-CPU-apart masks, and
// `sibling_pids()` supplies the targets. Mirrors
// `WorkType::CrossAffinityChurn` but shows a `Custom` worker can do it
// from the ctx alone. The asserts below are ADVISORY: an empty
// cpus/siblings (a wiring failure) panics the flipper → a sentinel
// report, but the scenario verdict does NOT gate on worker completion
// (src/scenario never inspects it), so they do not fail the test.
// Deterministic ctx-wiring correctness is pinned host-side by
// custom_worker_receives_wired_ctx; this e2e's job is "the ctx-driven
// flipper MECHANISM runs end-to-end against a live scheduler." Masks
// come from the flipper's own cpuset and the kernel clamps each
// sched_setaffinity to the target's cpuset (cpumask_and); a fully-
// disjoint sibling clamps to empty → -EINVAL, silently skipped — the
// shared CgroupDef below avoids that.
fn ctx_flipper(ctx: &WorkerCtx) -> WorkerReport {
let cpus = ctx.cpus();
assert!(
cpus.len() >= 2,
"ctx.cpus() must expose the cgroup's >=2-CPU cpuset; got {cpus:?}"
);
assert!(
!ctx.sibling_pids().is_empty(),
"ctx.sibling_pids() must see the cgroup peers in a dedicated cgroup"
);
let sz = std::mem::size_of::<libc::cpu_set_t>();
let make = |drop_last: bool| -> libc::cpu_set_t {
let take = if drop_last {
cpus.len() - 1
} else {
cpus.len()
};
let mut s: libc::cpu_set_t = unsafe { std::mem::zeroed() };
unsafe { libc::CPU_ZERO(&mut s) };
for &c in cpus.iter().take(take) {
unsafe { libc::CPU_SET(c, &mut s) };
}
s
};
let (mask_a, mask_b) = (make(false), make(true));
let mut toggle = false;
while !ctx.stop().load(Ordering::Relaxed) {
let mask: *const libc::cpu_set_t = if toggle { &mask_a } else { &mask_b };
for &pid in ctx.sibling_pids() {
// SAFETY: pid is a live cgroup sibling; sched_setaffinity
// with a valid mask pointer and size. Best-effort — a
// sibling that exited mid-run returns ESRCH, which we ignore.
unsafe {
libc::sched_setaffinity(pid, sz, mask);
}
}
toggle = !toggle;
}
WorkerReport::default()
}
/// A `Custom` worker drives cross-task affinity churn using ONLY its
/// `WorkerCtx` — `cpus()` for the masks, `sibling_pids()` for the
/// targets — in a real scx-ktstr VM: the ctx-driven flipper MECHANISM
/// runs end-to-end against a live scheduler. This does NOT prove
/// ctx-wiring correctness in-VM (a wiring bug panics the flipper's
/// advisory asserts → sentinel report, but the scenario can still run
/// cleanly — the verdict doesn't gate on worker completion); the
/// deterministic wiring guard is the host-side
/// `custom_worker_receives_wired_ctx`. Eight `FutexPingPong` targets
/// are declared BEFORE the two flippers so the declaration-order
/// contract makes them visible (see `WorkType::CrossAffinityChurn`).
/// Verdict mirrors `cross_affinity_churn_e2e`: the scenario runs
/// cleanly under the scheduler. Monitor event-rate ceilings
/// (select_cpu_fallback / dispatch_keep_last) are raised because
/// the flipper and futex profiles diverge by design.
#[ktstr_test(
scheduler = KTSTR_SCHED,
llcs = 1,
cores = 4,
threads = 1,
memory_mib = 2048,
sustained_samples = 25,
max_keep_last_rate = 1000000000.0,
max_fallback_rate = 1000000000.0
)]
fn worker_ctx_drives_affinity_churn_in_vm(ctx: &Ctx) -> Result<AssertResult> {
execute_defs(
ctx,
vec![
CgroupDef::named("race")
.cpuset(CpusetSpec::range(0.0, 1.0))
// Targets: the workers whose affinity the flippers rewrite.
.work(
WorkSpec::default()
.workers(8)
.work_type(WorkType::FutexPingPong { spin_iters: 0 }),
)
// Flippers: a Custom worker reading cpus()/sibling_pids()
// from its WorkerCtx — declared AFTER the targets so the
// serial-start contract makes the futex workers visible.
.work(
WorkSpec::default()
.workers(2)
.work_type(WorkType::custom("ctx_flipper", ctx_flipper)),
),
],
)
}