1 /* SPDX-License-Identifier: GPL-2.0 */
2 /*
3 * Test SCX_KICK_WAIT forward progress under cyclic wait pressure.
4 *
5 * SCX_KICK_WAIT busy-waits until the target CPU enters the scheduling path.
6 * If multiple CPUs form a wait cycle (A waits for B, B waits for C, C waits
7 * for A), all CPUs deadlock unless the implementation breaks the cycle.
8 *
9 * This test creates that scenario: three CPUs are arranged in a ring. The BPF
10 * scheduler's ops.enqueue() kicks the next CPU in the ring with SCX_KICK_WAIT
11 * on every enqueue. Userspace pins 4 worker threads per CPU that loop calling
12 * sched_yield(), generating a steady stream of enqueues and thus sustained
13 * A->B->C->A kick_wait cycle pressure. The test passes if the system remains
14 * responsive for 5 seconds without the scheduler being killed by the watchdog.
15 */
16 #define _GNU_SOURCE
17
18 #include <bpf/bpf.h>
19 #include <errno.h>
20 #include <pthread.h>
21 #include <sched.h>
22 #include <scx/common.h>
23 #include <stdint.h>
24 #include <string.h>
25 #include <time.h>
26 #include <unistd.h>
27
28 #include "scx_test.h"
29 #include "cyclic_kick_wait.bpf.skel.h"
30
31 #define WORKERS_PER_CPU 4
32 #define NR_TEST_CPUS 3
33 #define NR_WORKERS (NR_TEST_CPUS * WORKERS_PER_CPU)
34
35 struct worker_ctx {
36 pthread_t tid;
37 int cpu;
38 volatile bool stop;
39 volatile __u64 iters;
40 bool started;
41 };
42
worker_fn(void * arg)43 static void *worker_fn(void *arg)
44 {
45 struct worker_ctx *worker = arg;
46 cpu_set_t mask;
47
48 CPU_ZERO(&mask);
49 CPU_SET(worker->cpu, &mask);
50
51 if (sched_setaffinity(0, sizeof(mask), &mask))
52 return (void *)(uintptr_t)errno;
53
54 while (!worker->stop) {
55 sched_yield();
56 worker->iters++;
57 }
58
59 return NULL;
60 }
61
join_worker(struct worker_ctx * worker)62 static int join_worker(struct worker_ctx *worker)
63 {
64 void *ret;
65 struct timespec ts;
66 int err;
67
68 if (!worker->started)
69 return 0;
70
71 if (clock_gettime(CLOCK_REALTIME, &ts))
72 return -errno;
73
74 ts.tv_sec += 2;
75 err = pthread_timedjoin_np(worker->tid, &ret, &ts);
76 if (err == ETIMEDOUT)
77 pthread_detach(worker->tid);
78 if (err)
79 return -err;
80
81 if ((uintptr_t)ret)
82 return -(int)(uintptr_t)ret;
83
84 return 0;
85 }
86
setup(void ** ctx)87 static enum scx_test_status setup(void **ctx)
88 {
89 struct cyclic_kick_wait *skel;
90
91 skel = cyclic_kick_wait__open();
92 SCX_FAIL_IF(!skel, "Failed to open skel");
93 SCX_ENUM_INIT(skel);
94
95 *ctx = skel;
96 return SCX_TEST_PASS;
97 }
98
run(void * ctx)99 static enum scx_test_status run(void *ctx)
100 {
101 struct cyclic_kick_wait *skel = ctx;
102 struct worker_ctx workers[NR_WORKERS] = {};
103 struct bpf_link *link = NULL;
104 enum scx_test_status status = SCX_TEST_PASS;
105 int test_cpus[NR_TEST_CPUS];
106 int nr_cpus = 0;
107 cpu_set_t mask;
108 int ret, i;
109
110 if (sched_getaffinity(0, sizeof(mask), &mask)) {
111 SCX_ERR("Failed to get affinity (%d)", errno);
112 return SCX_TEST_FAIL;
113 }
114
115 for (i = 0; i < CPU_SETSIZE; i++) {
116 if (CPU_ISSET(i, &mask))
117 test_cpus[nr_cpus++] = i;
118 if (nr_cpus == NR_TEST_CPUS)
119 break;
120 }
121
122 if (nr_cpus < NR_TEST_CPUS)
123 return SCX_TEST_SKIP;
124
125 skel->rodata->test_cpu_a = test_cpus[0];
126 skel->rodata->test_cpu_b = test_cpus[1];
127 skel->rodata->test_cpu_c = test_cpus[2];
128
129 if (cyclic_kick_wait__load(skel)) {
130 SCX_ERR("Failed to load skel");
131 return SCX_TEST_FAIL;
132 }
133
134 link = bpf_map__attach_struct_ops(skel->maps.cyclic_kick_wait_ops);
135 if (!link) {
136 SCX_ERR("Failed to attach scheduler");
137 return SCX_TEST_FAIL;
138 }
139
140 for (i = 0; i < NR_WORKERS; i++)
141 workers[i].cpu = test_cpus[i / WORKERS_PER_CPU];
142
143 for (i = 0; i < NR_WORKERS; i++) {
144 ret = pthread_create(&workers[i].tid, NULL, worker_fn, &workers[i]);
145 if (ret) {
146 SCX_ERR("Failed to create worker thread %d (%d)", i, ret);
147 status = SCX_TEST_FAIL;
148 goto out;
149 }
150 workers[i].started = true;
151 }
152
153 sleep(5);
154
155 if (skel->data->uei.kind != EXIT_KIND(SCX_EXIT_NONE)) {
156 SCX_ERR("Scheduler exited unexpectedly (kind=%llu code=%lld)",
157 (unsigned long long)skel->data->uei.kind,
158 (long long)skel->data->uei.exit_code);
159 status = SCX_TEST_FAIL;
160 }
161
162 out:
163 for (i = 0; i < NR_WORKERS; i++)
164 workers[i].stop = true;
165
166 for (i = 0; i < NR_WORKERS; i++) {
167 ret = join_worker(&workers[i]);
168 if (ret && status == SCX_TEST_PASS) {
169 SCX_ERR("Failed to join worker thread %d (%d)", i, ret);
170 status = SCX_TEST_FAIL;
171 }
172 }
173
174 if (link)
175 bpf_link__destroy(link);
176
177 return status;
178 }
179
cleanup(void * ctx)180 static void cleanup(void *ctx)
181 {
182 struct cyclic_kick_wait *skel = ctx;
183
184 cyclic_kick_wait__destroy(skel);
185 }
186
187 struct scx_test cyclic_kick_wait = {
188 .name = "cyclic_kick_wait",
189 .description = "Verify SCX_KICK_WAIT forward progress under a 3-CPU wait cycle",
190 .setup = setup,
191 .run = run,
192 .cleanup = cleanup,
193 };
194 REGISTER_SCX_TEST(&cyclic_kick_wait)
195