selftests/sched_ext: Test that ops.dequeue() can iterate the consumed DSQ

Add a scheduler whose ops.dequeue() iterates the source user DSQ with
bpf_iter_scx_dsq. The iteration takes the DSQ's raw spinlock; on a
kernel that runs ops.dequeue() while the consume path still holds that
lock, the first task consumed self-deadlocks the CPU with IRQs
disabled. The watchdog cannot recover from that state, so on an
unfixed kernel this test wedges the system instead of failing cleanly.
On a fixed kernel the scheduler runs clean and the test passes.

Signed-off-by: fangqiurong <fangqiurong@kylinos.cn>
Signed-off-by: Tejun Heo <tj@kernel.org>
This commit is contained in:
fangqiurong 2026-09-17 15:52:42 +08:00 committed by Tejun Heo
parent cb86607ada
commit 9ec7ba20c9
3 changed files with 153 additions and 0 deletions

View File

@ -164,6 +164,7 @@ all_test_bpfprogs := $(foreach prog,$(wildcard *.bpf.c),$(INCLUDE_DIR)/$(patsubs
auto-test-targets := \
create_dsq \
dequeue \
dequeue_iter \
enq_last_no_enq_fails \
ddsp_bogus_dsq_fail \
ddsp_vtimelocal_fail \

View File

@ -0,0 +1,73 @@
// SPDX-License-Identifier: GPL-2.0
/*
* ops.dequeue() of this scheduler iterates the user DSQ it consumes
* tasks from with bpf_iter_scx_dsq, which takes the DSQ lock.
* On a kernel that still runs ops.dequeue() with that lock held, the
* iteration self-deadlocks the CPU - this test wedges the system on
* unfixed kernels instead of failing cleanly.
*
* Copyright (c) 2026 fangqiurong <fangqiurong@kylinos.cn>
*/
#include <scx/common.bpf.h>
char _license[] SEC("license") = "GPL";
UEI_DEFINE(uei);
#define TEST_DSQ_ID 1000
u64 dq_count;
s32 BPF_STRUCT_OPS_SLEEPABLE(dequeue_iter_init)
{
return scx_bpf_create_dsq(TEST_DSQ_ID, -1);
}
s32 BPF_STRUCT_OPS(dequeue_iter_select_cpu, struct task_struct *p,
s32 prev_cpu, u64 wake_flags)
{
return prev_cpu;
}
void BPF_STRUCT_OPS(dequeue_iter_enqueue, struct task_struct *p, u64 enq_flags)
{
scx_bpf_dsq_insert(p, TEST_DSQ_ID, SCX_SLICE_DFL, enq_flags);
}
void BPF_STRUCT_OPS(dequeue_iter_dispatch, s32 cpu, struct task_struct *task)
{
scx_bpf_dsq_move_to_local(TEST_DSQ_ID, 0);
}
void BPF_STRUCT_OPS(dequeue_iter_dequeue, struct task_struct *p, u64 deq_flags)
{
struct bpf_iter_scx_dsq it;
struct task_struct *t;
if (!bpf_iter_scx_dsq_new(&it, TEST_DSQ_ID, 0)) {
while ((t = bpf_iter_scx_dsq_next(&it)))
;
}
bpf_iter_scx_dsq_destroy(&it);
__sync_fetch_and_add(&dq_count, 1);
}
void BPF_STRUCT_OPS(dequeue_iter_exit, struct scx_exit_info *ei)
{
UEI_RECORD(uei, ei);
scx_bpf_destroy_dsq(TEST_DSQ_ID);
}
SEC(".struct_ops.link")
struct sched_ext_ops dequeue_iter_ops = {
.init = (void *)dequeue_iter_init,
.select_cpu = (void *)dequeue_iter_select_cpu,
.enqueue = (void *)dequeue_iter_enqueue,
.dispatch = (void *)dequeue_iter_dispatch,
.dequeue = (void *)dequeue_iter_dequeue,
.exit = (void *)dequeue_iter_exit,
.timeout_ms = 1000U,
.name = "dequeue_iter",
};

View File

@ -0,0 +1,79 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2026 fangqiurong <fangqiurong@kylinos.cn>
*/
#include <bpf/bpf.h>
#include <scx/common.h>
#include <time.h>
#include <unistd.h>
#include "dequeue_iter.bpf.skel.h"
#include "scx_test.h"
#define DQ_TARGET 10
#define DQ_DEADLINE_MS 3000
static unsigned long long now_ms(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return ts.tv_sec * 1000ULL + ts.tv_nsec / 1000000;
}
static enum scx_test_status setup(void **ctx)
{
struct dequeue_iter *skel;
skel = dequeue_iter__open();
SCX_FAIL_IF(!skel, "Failed to open");
SCX_ENUM_INIT(skel);
SCX_FAIL_IF(dequeue_iter__load(skel), "Failed to load skel");
*ctx = skel;
return SCX_TEST_PASS;
}
static enum scx_test_status run(void *ctx)
{
struct dequeue_iter *skel = ctx;
struct bpf_link *link;
unsigned long long end;
link = bpf_map__attach_struct_ops(skel->maps.dequeue_iter_ops);
SCX_FAIL_IF(!link, "Failed to attach scheduler");
end = now_ms() + DQ_DEADLINE_MS;
while (skel->bss->dq_count < DQ_TARGET && !UEI_EXITED(skel, uei) &&
now_ms() < end)
usleep(100);
bpf_link__destroy(link);
SCX_EQ(skel->data->uei.kind, EXIT_KIND(SCX_EXIT_UNREG));
if (skel->bss->dq_count < DQ_TARGET) {
SCX_ERR("ops.dequeue() fired only %llu times",
(unsigned long long)skel->bss->dq_count);
return SCX_TEST_FAIL;
}
return SCX_TEST_PASS;
}
static void cleanup(void *ctx)
{
struct dequeue_iter *skel = ctx;
dequeue_iter__destroy(skel);
}
struct scx_test dequeue_iter = {
.name = "dequeue_iter",
.description = "Verify ops.dequeue() can iterate its source user DSQ",
.setup = setup,
.run = run,
.cleanup = cleanup,
};
REGISTER_SCX_TEST(&dequeue_iter)