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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
|
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Test SCX_KICK_WAIT forward progress under cyclic wait pressure.
*
* SCX_KICK_WAIT busy-waits until the target CPU enters the scheduling path.
* If multiple CPUs form a wait cycle (A waits for B, B waits for C, C waits
* for A), all CPUs deadlock unless the implementation breaks the cycle.
*
* This test creates that scenario: three CPUs are arranged in a ring. The BPF
* scheduler's ops.enqueue() kicks the next CPU in the ring with SCX_KICK_WAIT
* on every enqueue. Userspace pins 4 worker threads per CPU that loop calling
* sched_yield(), generating a steady stream of enqueues and thus sustained
* A->B->C->A kick_wait cycle pressure. The test passes if the system remains
* responsive for 5 seconds without the scheduler being killed by the watchdog.
*/
#define _GNU_SOURCE
#include <bpf/bpf.h>
#include <errno.h>
#include <pthread.h>
#include <sched.h>
#include <scx/common.h>
#include <stdint.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include "scx_test.h"
#include "cyclic_kick_wait.bpf.skel.h"
#define WORKERS_PER_CPU 4
#define NR_TEST_CPUS 3
#define NR_WORKERS (NR_TEST_CPUS * WORKERS_PER_CPU)
struct worker_ctx {
pthread_t tid;
int cpu;
volatile bool stop;
volatile __u64 iters;
bool started;
};
static void *worker_fn(void *arg)
{
struct worker_ctx *worker = arg;
cpu_set_t mask;
CPU_ZERO(&mask);
CPU_SET(worker->cpu, &mask);
if (sched_setaffinity(0, sizeof(mask), &mask))
return (void *)(uintptr_t)errno;
while (!worker->stop) {
sched_yield();
worker->iters++;
}
return NULL;
}
static int join_worker(struct worker_ctx *worker)
{
void *ret;
struct timespec ts;
int err;
if (!worker->started)
return 0;
if (clock_gettime(CLOCK_REALTIME, &ts))
return -errno;
ts.tv_sec += 2;
err = pthread_timedjoin_np(worker->tid, &ret, &ts);
if (err == ETIMEDOUT)
pthread_detach(worker->tid);
if (err)
return -err;
if ((uintptr_t)ret)
return -(int)(uintptr_t)ret;
return 0;
}
static enum scx_test_status setup(void **ctx)
{
struct cyclic_kick_wait *skel;
skel = cyclic_kick_wait__open();
SCX_FAIL_IF(!skel, "Failed to open skel");
SCX_ENUM_INIT(skel);
*ctx = skel;
return SCX_TEST_PASS;
}
static enum scx_test_status run(void *ctx)
{
struct cyclic_kick_wait *skel = ctx;
struct worker_ctx workers[NR_WORKERS] = {};
struct bpf_link *link = NULL;
enum scx_test_status status = SCX_TEST_PASS;
int test_cpus[NR_TEST_CPUS];
int nr_cpus = 0;
cpu_set_t mask;
int ret, i;
if (sched_getaffinity(0, sizeof(mask), &mask)) {
SCX_ERR("Failed to get affinity (%d)", errno);
return SCX_TEST_FAIL;
}
for (i = 0; i < CPU_SETSIZE; i++) {
if (CPU_ISSET(i, &mask))
test_cpus[nr_cpus++] = i;
if (nr_cpus == NR_TEST_CPUS)
break;
}
if (nr_cpus < NR_TEST_CPUS)
return SCX_TEST_SKIP;
skel->rodata->test_cpu_a = test_cpus[0];
skel->rodata->test_cpu_b = test_cpus[1];
skel->rodata->test_cpu_c = test_cpus[2];
if (cyclic_kick_wait__load(skel)) {
SCX_ERR("Failed to load skel");
return SCX_TEST_FAIL;
}
link = bpf_map__attach_struct_ops(skel->maps.cyclic_kick_wait_ops);
if (!link) {
SCX_ERR("Failed to attach scheduler");
return SCX_TEST_FAIL;
}
for (i = 0; i < NR_WORKERS; i++)
workers[i].cpu = test_cpus[i / WORKERS_PER_CPU];
for (i = 0; i < NR_WORKERS; i++) {
ret = pthread_create(&workers[i].tid, NULL, worker_fn, &workers[i]);
if (ret) {
SCX_ERR("Failed to create worker thread %d (%d)", i, ret);
status = SCX_TEST_FAIL;
goto out;
}
workers[i].started = true;
}
sleep(5);
if (skel->data->uei.kind != EXIT_KIND(SCX_EXIT_NONE)) {
SCX_ERR("Scheduler exited unexpectedly (kind=%llu code=%lld)",
(unsigned long long)skel->data->uei.kind,
(long long)skel->data->uei.exit_code);
status = SCX_TEST_FAIL;
}
out:
for (i = 0; i < NR_WORKERS; i++)
workers[i].stop = true;
for (i = 0; i < NR_WORKERS; i++) {
ret = join_worker(&workers[i]);
if (ret && status == SCX_TEST_PASS) {
SCX_ERR("Failed to join worker thread %d (%d)", i, ret);
status = SCX_TEST_FAIL;
}
}
if (link)
bpf_link__destroy(link);
return status;
}
static void cleanup(void *ctx)
{
struct cyclic_kick_wait *skel = ctx;
cyclic_kick_wait__destroy(skel);
}
struct scx_test cyclic_kick_wait = {
.name = "cyclic_kick_wait",
.description = "Verify SCX_KICK_WAIT forward progress under a 3-CPU wait cycle",
.setup = setup,
.run = run,
.cleanup = cleanup,
};
REGISTER_SCX_TEST(&cyclic_kick_wait)
|