diff options
| author | Tejun Heo <tj@kernel.org> | 2026-10-14 08:18:11 -1000 |
|---|---|---|
| committer | Tejun Heo <tj@kernel.org> | 2026-10-14 08:18:11 -1000 |
| commit | cf5bc5805b23fa88413110fefca4d5c699d596ec (patch) | |
| tree | d725b1014af0393bd983da442a7075422a7b6cdb | |
| parent | 070b82a8012249bd8bb83c68b67683c707eb8ca0 (diff) | |
| parent | 6052353d2f32fd876ac0846b089f292dd5793141 (diff) | |
| download | linux-next-cf5bc5805b23fa88413110fefca4d5c699d596ec.tar.gz linux-next-cf5bc5805b23fa88413110fefca4d5c699d596ec.zip | |
Merge branch 'for-7.4' into for-next
18 files changed, 162 insertions, 33 deletions
diff --git a/tools/sched_ext/include/scx/user_exit_info.h b/tools/sched_ext/include/scx/user_exit_info.h index 56a02b549aef..cf27ac2447ef 100644 --- a/tools/sched_ext/include/scx/user_exit_info.h +++ b/tools/sched_ext/include/scx/user_exit_info.h @@ -52,6 +52,7 @@ enum scx_exit_code { /* Reasons */ SCX_ECODE_RSN_HOTPLUG = 1LLU << 32, + SCX_ECODE_RSN_CGROUP_OFFLINE = 2LLU << 32, /* Actions */ SCX_ECODE_ACT_RESTART = 1LLU << 48, diff --git a/tools/testing/selftests/sched_ext/allowed_cpus.c b/tools/testing/selftests/sched_ext/allowed_cpus.c index 773699d120ea..76311728a6b1 100644 --- a/tools/testing/selftests/sched_ext/allowed_cpus.c +++ b/tools/testing/selftests/sched_ext/allowed_cpus.c @@ -149,7 +149,7 @@ static enum scx_test_status run(void *ctx) /* Just sleeping is fine, plenty of scheduling events happening. */ sleep(1); - if (skel->data->uei.kind != EXIT_KIND(SCX_EXIT_NONE)) { + if (UEI_EXITED(skel, uei)) { SCX_ERR("Scheduler exited unexpectedly"); goto out; } diff --git a/tools/testing/selftests/sched_ext/cgroup_nr_cpus.c b/tools/testing/selftests/sched_ext/cgroup_nr_cpus.c index 4973f7080353..1475a8b3f05f 100644 --- a/tools/testing/selftests/sched_ext/cgroup_nr_cpus.c +++ b/tools/testing/selftests/sched_ext/cgroup_nr_cpus.c @@ -400,7 +400,7 @@ static enum scx_test_status run(void *arg) !wait_nr_cpus(&ctx, ctx.leaf, nr_child, "leaf after cpuset disable")) goto out; - if (ctx.skel->data->uei.kind != EXIT_KIND(SCX_EXIT_NONE)) { + if (UEI_EXITED(ctx.skel, uei)) { SCX_ERR("Scheduler exited unexpectedly"); goto out; } diff --git a/tools/testing/selftests/sched_ext/create_dsq.bpf.c b/tools/testing/selftests/sched_ext/create_dsq.bpf.c index 680cc4b6d8c7..24dacd1aab0c 100644 --- a/tools/testing/selftests/sched_ext/create_dsq.bpf.c +++ b/tools/testing/selftests/sched_ext/create_dsq.bpf.c @@ -12,6 +12,8 @@ char _license[] SEC("license") = "GPL"; u32 nr_lifecycle_tests; +UEI_DEFINE(uei); + void BPF_STRUCT_OPS(create_dsq_exit_task, struct task_struct *p, struct scx_exit_task_args *args) { @@ -84,10 +86,16 @@ s32 BPF_STRUCT_OPS_SLEEPABLE(create_dsq_init) return 0; } +void BPF_STRUCT_OPS(create_dsq_exit, struct scx_exit_info *ei) +{ + UEI_RECORD(uei, ei); +} + SEC(".struct_ops.link") struct sched_ext_ops create_dsq_ops = { .init_task = (void *) create_dsq_init_task, .exit_task = (void *) create_dsq_exit_task, .init = (void *) create_dsq_init, + .exit = (void *) create_dsq_exit, .name = "create_dsq", }; diff --git a/tools/testing/selftests/sched_ext/create_dsq.c b/tools/testing/selftests/sched_ext/create_dsq.c index 422e6532ee71..84143d5710f4 100644 --- a/tools/testing/selftests/sched_ext/create_dsq.c +++ b/tools/testing/selftests/sched_ext/create_dsq.c @@ -35,6 +35,8 @@ static enum scx_test_status run(void *ctx) return SCX_TEST_FAIL; } + SCX_ASSERT_ALIVE(skel, uei); + bpf_link__destroy(link); SCX_EQ(skel->bss->nr_lifecycle_tests, 1024); diff --git a/tools/testing/selftests/sched_ext/cyclic_kick_wait.c b/tools/testing/selftests/sched_ext/cyclic_kick_wait.c index c2e5aa9de715..178d1d75f332 100644 --- a/tools/testing/selftests/sched_ext/cyclic_kick_wait.c +++ b/tools/testing/selftests/sched_ext/cyclic_kick_wait.c @@ -152,7 +152,7 @@ static enum scx_test_status run(void *ctx) sleep(5); - if (skel->data->uei.kind != EXIT_KIND(SCX_EXIT_NONE)) { + if (UEI_EXITED(skel, uei)) { SCX_ERR("Scheduler exited unexpectedly (kind=%llu code=%lld)", (unsigned long long)skel->data->uei.kind, (long long)skel->data->uei.exit_code); diff --git a/tools/testing/selftests/sched_ext/enq_blocked.c b/tools/testing/selftests/sched_ext/enq_blocked.c index 26204548bbf0..1d5e090b54bc 100644 --- a/tools/testing/selftests/sched_ext/enq_blocked.c +++ b/tools/testing/selftests/sched_ext/enq_blocked.c @@ -844,7 +844,7 @@ out: } } - if (skel->data->uei.kind != EXIT_KIND(SCX_EXIT_NONE)) { + if (UEI_EXITED(skel, uei)) { SCX_ERR("Scheduler exited unexpectedly (kind=%llu code=%lld)", (unsigned long long)skel->data->uei.kind, (long long)skel->data->uei.exit_code); diff --git a/tools/testing/selftests/sched_ext/exit.c b/tools/testing/selftests/sched_ext/exit.c index 01b17092d5c8..fd6f1c9ab2db 100644 --- a/tools/testing/selftests/sched_ext/exit.c +++ b/tools/testing/selftests/sched_ext/exit.c @@ -43,8 +43,12 @@ static enum scx_test_status run(void *ctx) } /* Assumes uei.kind is written last */ - while (skel->data->uei.kind == EXIT_KIND(SCX_EXIT_NONE)) - sched_yield(); + if (!SCX_TIMED_WAIT_UEI(skel, uei, 10000)) { + SCX_ERR("Timed out waiting for scheduler to exit"); + bpf_link__destroy(link); + exit__destroy(skel); + return SCX_TEST_FAIL; + } SCX_EQ(skel->data->uei.kind, EXIT_KIND(SCX_EXIT_UNREG_BPF)); SCX_EQ(skel->data->uei.exit_code, tc); diff --git a/tools/testing/selftests/sched_ext/hotplug.c b/tools/testing/selftests/sched_ext/hotplug.c index 10b8d42bd89b..3c07686a93c8 100644 --- a/tools/testing/selftests/sched_ext/hotplug.c +++ b/tools/testing/selftests/sched_ext/hotplug.c @@ -85,8 +85,10 @@ static enum scx_test_status test_hotplug(bool onlining, bool cbs_defined) if (toggle_online_status(onlining ? 1 : 0)) goto out_destroy_link; - while (!UEI_EXITED(skel, uei)) - sched_yield(); + if (!SCX_TIMED_WAIT_UEI(skel, uei, 10000)) { + SCX_ERR("Timed out waiting for scheduler to exit"); + goto out_destroy_link; + } SCX_EQ(skel->data->uei.kind, kind); SCX_EQ(UEI_REPORT(skel, uei), code); @@ -130,8 +132,10 @@ static enum scx_test_status test_hotplug_attach(void) goto out_destroy_link; SCX_ASSERT(link); - while (!UEI_EXITED(skel, uei)) - sched_yield(); + if (!SCX_TIMED_WAIT_UEI(skel, uei, 10000)) { + SCX_ERR("Timed out waiting for scheduler to exit"); + goto out_destroy_link; + } kind = SCX_KIND_VAL(SCX_EXIT_UNREG_KERN); code = SCX_ECODE_VAL(SCX_ECODE_ACT_RESTART) | diff --git a/tools/testing/selftests/sched_ext/init_enable_count.bpf.c b/tools/testing/selftests/sched_ext/init_enable_count.bpf.c index 5eb9edb1837d..c0a5dd8fe183 100644 --- a/tools/testing/selftests/sched_ext/init_enable_count.bpf.c +++ b/tools/testing/selftests/sched_ext/init_enable_count.bpf.c @@ -15,6 +15,8 @@ char _license[] SEC("license") = "GPL"; u64 init_task_cnt, exit_task_cnt, enable_cnt, disable_cnt; u64 init_fork_cnt, init_transition_cnt; +UEI_DEFINE(uei); + s32 BPF_STRUCT_OPS_SLEEPABLE(cnt_init_task, struct task_struct *p, struct scx_init_task_args *args) { @@ -43,11 +45,17 @@ void BPF_STRUCT_OPS(cnt_disable, struct task_struct *p) __sync_fetch_and_add(&disable_cnt, 1); } +void BPF_STRUCT_OPS(cnt_exit, struct scx_exit_info *ei) +{ + UEI_RECORD(uei, ei); +} + SEC(".struct_ops.link") struct sched_ext_ops init_enable_count_ops = { .init_task = (void *) cnt_init_task, .exit_task = (void *) cnt_exit_task, .enable = (void *) cnt_enable, .disable = (void *) cnt_disable, + .exit = (void *) cnt_exit, .name = "init_enable_count", }; diff --git a/tools/testing/selftests/sched_ext/init_enable_count.c b/tools/testing/selftests/sched_ext/init_enable_count.c index 44577e30e764..c4a57038a1d6 100644 --- a/tools/testing/selftests/sched_ext/init_enable_count.c +++ b/tools/testing/selftests/sched_ext/init_enable_count.c @@ -5,6 +5,7 @@ * Copyright (c) 2023 Tejun Heo <tj@kernel.org> */ #include <signal.h> +#include <string.h> #include <stdio.h> #include <unistd.h> #include <sched.h> @@ -72,7 +73,17 @@ static enum scx_test_status run_test(bool global) close(pipe_fds[1]); signal(SIGCHLD, SIG_DFL); + SCX_ASSERT_ALIVE(skel, uei); + bpf_link__destroy(link); + + /* + * The clean unregister above is recorded in uei and would + * otherwise trip the liveness check of the second cycle, which + * reuses this skeleton. + */ + memset(&skel->data->uei, 0, sizeof(skel->data->uei)); + SCX_GE(skel->bss->init_task_cnt, num_pre_forks); SCX_GE(skel->bss->exit_task_cnt, num_pre_forks); @@ -124,6 +135,8 @@ static enum scx_test_status run_test(bool global) status); } + SCX_ASSERT_ALIVE(skel, uei); + bpf_link__destroy(link); SCX_GE(skel->bss->init_task_cnt, 2 * num_children); diff --git a/tools/testing/selftests/sched_ext/kick.c b/tools/testing/selftests/sched_ext/kick.c index 7f01602c75b2..40c91d54c1eb 100644 --- a/tools/testing/selftests/sched_ext/kick.c +++ b/tools/testing/selftests/sched_ext/kick.c @@ -97,7 +97,7 @@ static bool wait_for_state(struct kick *skel, u32 wanted) for (i = 0; i < WAIT_LOOPS; i++) { if (__atomic_load_n(&skel->bss->state, __ATOMIC_ACQUIRE) == wanted) return true; - if (skel->data->uei.kind != EXIT_KIND(SCX_EXIT_NONE)) + if (UEI_EXITED(skel, uei)) return false; usleep(1000); } @@ -111,7 +111,7 @@ static bool wait_for_counter(struct kick *skel, const u64 *counter, u64 wanted) for (i = 0; i < WAIT_LOOPS; i++) { if (__atomic_load_n(counter, __ATOMIC_ACQUIRE) >= wanted) return true; - if (skel->data->uei.kind != EXIT_KIND(SCX_EXIT_NONE)) + if (UEI_EXITED(skel, uei)) return false; usleep(1000); } @@ -421,7 +421,9 @@ static enum scx_test_status invalid_one(struct kick_ctx *ctx, u32 scenario) enum scx_test_status ret = SCX_TEST_FAIL; int cpu = ctx->target_cpu; int i; + u64 error_kind; + error_kind = EXIT_KIND(SCX_EXIT_ERROR); victim = scx_test_spawn_gated_worker(cpu, false); if (victim.pid < 0) return SCX_TEST_FAIL; @@ -440,7 +442,7 @@ static enum scx_test_status invalid_one(struct kick_ctx *ctx, u32 scenario) goto out; for (i = 0; i < WAIT_LOOPS; i++) { - if (skel->data->uei.kind == EXIT_KIND(SCX_EXIT_ERROR)) { + if (UEI_EXITED(skel, uei) == error_kind) { ret = SCX_TEST_PASS; break; } diff --git a/tools/testing/selftests/sched_ext/maximal.bpf.c b/tools/testing/selftests/sched_ext/maximal.bpf.c index 04a369078aac..9ea664e2ff70 100644 --- a/tools/testing/selftests/sched_ext/maximal.bpf.c +++ b/tools/testing/selftests/sched_ext/maximal.bpf.c @@ -14,6 +14,8 @@ char _license[] SEC("license") = "GPL"; #define DSQ_ID 0 +UEI_DEFINE(uei); + s32 BPF_STRUCT_OPS(maximal_select_cpu, struct task_struct *p, s32 prev_cpu, u64 wake_flags) { @@ -132,7 +134,9 @@ s32 BPF_STRUCT_OPS_SLEEPABLE(maximal_init) } void BPF_STRUCT_OPS(maximal_exit, struct scx_exit_info *info) -{} +{ + UEI_RECORD(uei, info); +} SEC(".struct_ops.link") struct sched_ext_ops maximal_ops = { diff --git a/tools/testing/selftests/sched_ext/maximal.c b/tools/testing/selftests/sched_ext/maximal.c index 1dc369224670..6c6b80e0446e 100644 --- a/tools/testing/selftests/sched_ext/maximal.c +++ b/tools/testing/selftests/sched_ext/maximal.c @@ -35,6 +35,8 @@ static enum scx_test_status run(void *ctx) link = bpf_map__attach_struct_ops(skel->maps.maximal_ops); SCX_FAIL_IF(!link, "Failed to attach scheduler"); + SCX_ASSERT_ALIVE(skel, uei); + bpf_link__destroy(link); return SCX_TEST_PASS; diff --git a/tools/testing/selftests/sched_ext/nohz_tick.c b/tools/testing/selftests/sched_ext/nohz_tick.c index df40976b9166..e19a97034b7f 100644 --- a/tools/testing/selftests/sched_ext/nohz_tick.c +++ b/tools/testing/selftests/sched_ext/nohz_tick.c @@ -165,7 +165,7 @@ static bool wait_for_counter(struct nohz_tick *skel, const u64 *counter, u64 val for (elapsed = 0; elapsed < timeout_ms; elapsed++) { if (__atomic_load_n(counter, __ATOMIC_RELAXED) >= value) return true; - if (skel->data->uei.kind != EXIT_KIND(SCX_EXIT_NONE)) + if (UEI_EXITED(skel, uei)) return false; usleep(1000); } @@ -182,7 +182,7 @@ static bool wait_for_tick_stop(struct nohz_tick *skel, const u64 *counter, int t u64 curr; usleep(1000); - if (skel->data->uei.kind != EXIT_KIND(SCX_EXIT_NONE)) + if (UEI_EXITED(skel, uei)) return false; curr = __atomic_load_n(counter, __ATOMIC_RELAXED); if (curr == prev) { @@ -461,7 +461,7 @@ static enum scx_test_status run(void *ctx_ptr) scx_test_stop_gated_worker(&challenger); check_exit: - if (skel->data->uei.kind != EXIT_KIND(SCX_EXIT_NONE)) { + if (UEI_EXITED(skel, uei)) { SCX_ERR("Scheduler exited unexpectedly (kind=%llu code=%lld)", (unsigned long long)skel->data->uei.kind, (long long)skel->data->uei.exit_code); diff --git a/tools/testing/selftests/sched_ext/prog_run.c b/tools/testing/selftests/sched_ext/prog_run.c index 1129ec2aaddc..369c744ac06e 100644 --- a/tools/testing/selftests/sched_ext/prog_run.c +++ b/tools/testing/selftests/sched_ext/prog_run.c @@ -55,8 +55,11 @@ static enum scx_test_status run(void *ctx) } /* Assumes uei.kind is written last */ - while (skel->data->uei.kind == EXIT_KIND(SCX_EXIT_NONE)) - sched_yield(); + if (!SCX_TIMED_WAIT_UEI(skel, uei, 10000)) { + SCX_ERR("Timed out waiting for scheduler to exit"); + status = SCX_TEST_FAIL; + goto out; + } if (skel->data->uei.kind != EXIT_KIND(SCX_EXIT_UNREG_BPF)) { SCX_ERR("Unexpected exit kind: %llu", diff --git a/tools/testing/selftests/sched_ext/reload_loop.c b/tools/testing/selftests/sched_ext/reload_loop.c index 49297b83d748..7d8880fa1a03 100644 --- a/tools/testing/selftests/sched_ext/reload_loop.c +++ b/tools/testing/selftests/sched_ext/reload_loop.c @@ -4,6 +4,7 @@ * Copyright (c) 2024 David Vernet <dvernet@meta.com> */ #include <bpf/bpf.h> +#include <errno.h> #include <pthread.h> #include <scx/common.h> #include <sys/wait.h> @@ -11,8 +12,14 @@ #include "maximal.bpf.skel.h" #include "scx_test.h" +enum { + RELOAD_ITERS = 1024, + RELOAD_THREADS = 2, +}; + static struct maximal *skel; -static pthread_t threads[2]; +static pthread_t threads[RELOAD_THREADS]; +static int unexpected_errno; bool force_exit = false; @@ -31,32 +38,58 @@ static enum scx_test_status setup(void **ctx) static void *do_reload_loop(void *arg) { - u32 i; + u32 i, failures = 0; - for (i = 0; i < 1024 && !force_exit; i++) { + for (i = 0; i < RELOAD_ITERS && !force_exit; i++) { struct bpf_link *link; + errno = 0; link = bpf_map__attach_struct_ops(skel->maps.maximal_ops); - if (link) - bpf_link__destroy(link); + if (!link) { + int e = errno; + + if (e != EBUSY) + __atomic_store_n(&unexpected_errno, e, + __ATOMIC_RELAXED); + failures++; + continue; + } + + bpf_link__destroy(link); } - return NULL; + return (void *)(unsigned long)failures; } static enum scx_test_status run(void *ctx) { - int err; + unsigned long failures[RELOAD_THREADS] = {}; + int err, i; void *ret; err = pthread_create(&threads[0], NULL, do_reload_loop, NULL); SCX_FAIL_IF(err, "Failed to create thread 0"); err = pthread_create(&threads[1], NULL, do_reload_loop, NULL); - SCX_FAIL_IF(err, "Failed to create thread 1"); + if (err) { + force_exit = true; + pthread_join(threads[0], NULL); + SCX_FAIL("Failed to create thread 1"); + } SCX_FAIL_IF(pthread_join(threads[0], &ret), "thread 0 failed"); + failures[0] = (unsigned long)ret; + SCX_FAIL_IF(pthread_join(threads[1], &ret), "thread 1 failed"); + failures[1] = (unsigned long)ret; + + SCX_FAIL_IF(unexpected_errno, "unexpected attach errno %d", + unexpected_errno); + + for (i = 0; i < RELOAD_THREADS; i++) + SCX_FAIL_IF(failures[i] == RELOAD_ITERS, + "thread %d: every attach failed (%lu EBUSY)", + i, failures[i]); return SCX_TEST_PASS; } diff --git a/tools/testing/selftests/sched_ext/scx_test.h b/tools/testing/selftests/sched_ext/scx_test.h index 90b8d6915bb7..8490b01fff74 100644 --- a/tools/testing/selftests/sched_ext/scx_test.h +++ b/tools/testing/selftests/sched_ext/scx_test.h @@ -18,8 +18,6 @@ enum scx_test_status { SCX_TEST_FAIL, }; -#define EXIT_KIND(__ent) __COMPAT_ENUM_OR_ZERO("scx_exit_kind", #__ent) - struct scx_test { /** * name - The name of the testcase. @@ -110,6 +108,41 @@ void scx_test_register(struct scx_test *test); #define SCX_ASSERT(_x) SCX_FAIL_IF(!(_x), "Expected %s to be true (%lu)", \ #_x, (u64)(_x)) +/* + * SCX_ASSERT_ALIVE - Fail the test if the scheduler under test exited + * on its own between attach and this check, e.g. from a runtime error. + * A scheduler that dies mid-test freezes whatever state the test is + * about to assert on, which can make the assertions pass vacuously. + * uei.msg carries the actual error text, so print it alongside the + * recorded kind and exit code. + */ +#define SCX_ASSERT_ALIVE(__skel, __uei) \ + do { \ + if (UEI_EXITED(__skel, __uei)) \ + SCX_FAIL("Scheduler exited unexpectedly" \ + " (kind=%llu code=%lld msg='%s')", \ + (unsigned long long)(__skel)->data->__uei.kind, \ + (long long)(__skel)->data->__uei.exit_code, \ + (__skel)->data->__uei.msg); \ + } while (0) + +/* + * SCX_TIMED_WAIT_UEI - Wait for the scheduler under test to exit, i.e. + * its uei to be filled in, for up to @__ms iterations at ~1ms each. + * Returns true if the scheduler exited and false on timeout, so a + * scheduler that never exits fails the test instead of hanging it. + * The bound is iteration-based rather than wall-clock and stretches + * on a loaded system instead of failing early. + */ +#define SCX_TIMED_WAIT_UEI(__skel, __uei, __ms) \ + ({ \ + s64 __left = (__ms); \ + \ + while (__left-- > 0 && !UEI_EXITED(__skel, __uei)) \ + usleep(1000); \ + UEI_EXITED(__skel, __uei) != 0; \ + }) + #define SCX_ECODE_VAL(__ecode) ({ \ u64 __val = 0; \ bool __found = false; \ @@ -119,13 +152,25 @@ void scx_test_register(struct scx_test *test); (s64)__val; \ }) +/* + * SCX_KIND_VAL - Resolve an scx_exit_kind enumerator from kernel BTF. + * + * Unlike __COMPAT_ENUM_OR_ZERO(), a missing enumerator fails the test + * loudly instead of silently resolving to 0 (== SCX_EXIT_NONE), which + * would make the comparison vacuous. + */ #define SCX_KIND_VAL(__kind) ({ \ - u64 __val = 0; \ - bool __found = false; \ + u64 __val = 0; \ \ - __found = __COMPAT_read_enum("scx_exit_kind", #__kind, &__val); \ - SCX_ASSERT(__found); \ + if (!__COMPAT_read_enum("scx_exit_kind", #__kind, &__val)) \ + SCX_FAIL("scx_exit_kind.%s not found in kernel BTF", #__kind); \ __val; \ }) +/* + * EXIT_KIND - Alias of SCX_KIND_VAL() for resolving scx_exit_kind + * enumerator values, kept for readability at the EXIT_KIND() call sites. + */ +#define EXIT_KIND(__ent) SCX_KIND_VAL(__ent) + #endif // # __SCX_TEST_H__ |
