* [PATCHSET sched_ext/for-7.2-fixes] sched_ext: Fix finite-slice ticks on nohz_full
@ 2026-07-06 16:18 Andrea Righi
2026-07-06 16:18 ` [PATCH 1/2] sched_ext: Enable tick for finite slices " Andrea Righi
2026-07-06 16:18 ` [PATCH 2/2] selftests/sched_ext: Verify nohz_full tick behavior Andrea Righi
0 siblings, 2 replies; 6+ messages in thread
From: Andrea Righi @ 2026-07-06 16:18 UTC (permalink / raw)
To: Tejun Heo, David Vernet, Changwoo Min
Cc: Joel Fernandes, sched-ext, linux-kernel
When an EXT task with a finite slice is scheduled on a nohz_full CPU, the
scheduler may incorrectly keep the tick disabled because it still observes the
outgoing task (e.g., the idle task) during the context switch. As a result, the
incoming task can run past its assigned slice.
Fix this by always enabling the scheduler tick for finite-slice EXT tasks and
provide a kselftest that reproduces the scenario and verifies that scheduler
ticks are delivered correctly. Test is skipped in kernels without available
nohz_full CPUS.
Test case (using virtme-ng):
$ vng -vb --config tools/testing/selftests/sched_ext/config \
--configitem CONFIG_NO_HZ_FULL=y
$ vng --append "nohz_full=all" -- \
tools/testing/selftests/sched_ext/runner -t nohz_tick
Before:
$ vng --append "nohz_full=all" -- tools/testing/selftests/sched_ext/runner -t nohz_tick
===== START =====
TEST: nohz_tick
DESCRIPTION: Verify finite EXT slices restart the NOHZ_FULL tick
OUTPUT:
ERR: nohz_tick.c:270
Finite-slice worker received only 1 scheduler ticks
not ok 1 nohz_tick #
===== END =====
=============================
RESULTS:
PASSED: 0
SKIPPED: 0
FAILED: 1
Failed tests:
- nohz_tick
After:
$ vng --append "nohz_full=all" -- tools/testing/selftests/sched_ext/runner -t nohz_tick
===== START =====
TEST: nohz_tick
DESCRIPTION: Verify finite EXT slices restart the NOHZ_FULL tick
OUTPUT:
CPU 1 received 4 finite-slice ticks
ok 1 nohz_tick #
===== END =====
=============================
RESULTS:
PASSED: 1
SKIPPED: 0
FAILED: 0
Andrea Righi (2):
sched_ext: Enable tick for finite slices on nohz_full
selftests/sched_ext: Verify nohz_full tick behavior
kernel/sched/ext/ext.c | 14 +-
tools/testing/selftests/sched_ext/Makefile | 1 +
tools/testing/selftests/sched_ext/nohz_tick.bpf.c | 65 +++++
tools/testing/selftests/sched_ext/nohz_tick.c | 309 ++++++++++++++++++++++
4 files changed, 386 insertions(+), 3 deletions(-)
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH 1/2] sched_ext: Enable tick for finite slices on nohz_full
2026-07-06 16:18 [PATCHSET sched_ext/for-7.2-fixes] sched_ext: Fix finite-slice ticks on nohz_full Andrea Righi
@ 2026-07-06 16:18 ` Andrea Righi
2026-07-06 22:09 ` Tejun Heo
2026-07-06 16:18 ` [PATCH 2/2] selftests/sched_ext: Verify nohz_full tick behavior Andrea Righi
1 sibling, 1 reply; 6+ messages in thread
From: Andrea Righi @ 2026-07-06 16:18 UTC (permalink / raw)
To: Tejun Heo, David Vernet, Changwoo Min
Cc: Joel Fernandes, sched-ext, linux-kernel
set_next_task_scx() updates the tick dependency before __schedule()
updates rq->curr. When switching from a non-EXT task, such as idle, to
an EXT task with a finite slice, sched_update_tick_dependency() checks
the outgoing task and can allow the tick to remain stopped. The incoming
task can then fail to receive the ticks needed to expire its slice.
A finite slice is sufficient by itself to require the tick, so set
TICK_DEP_BIT_SCHED directly when transitioning to a finite slice. Keep
using sched_update_tick_dependency() when transitioning to an infinite
slice so that the scheduler evaluates all runqueue requirements before
clearing the dependency.
Fixes: 22a920209ab6 ("sched_ext: Implement tickless support")
Signed-off-by: Andrea Righi <arighi@nvidia.com>
---
kernel/sched/ext/ext.c | 14 +++++++++++---
1 file changed, 11 insertions(+), 3 deletions(-)
diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c
index e75e2fd5ab7e3..dc20a8eed8f6c 100644
--- a/kernel/sched/ext/ext.c
+++ b/kernel/sched/ext/ext.c
@@ -2982,12 +2982,20 @@ static void set_next_task_scx(struct rq *rq, struct task_struct *p, bool first)
*/
if ((p->scx.slice == SCX_SLICE_INF) !=
(bool)(rq->scx.flags & SCX_RQ_CAN_STOP_TICK)) {
- if (p->scx.slice == SCX_SLICE_INF)
+ if (p->scx.slice == SCX_SLICE_INF) {
rq->scx.flags |= SCX_RQ_CAN_STOP_TICK;
- else
+ sched_update_tick_dependency(rq);
+ } else {
rq->scx.flags &= ~SCX_RQ_CAN_STOP_TICK;
- sched_update_tick_dependency(rq);
+ /*
+ * The rq still references the outgoing scheduling context.
+ * A finite slice is sufficient by itself to require the tick.
+ */
+ if (tick_nohz_full_cpu(cpu_of(rq)))
+ tick_nohz_dep_set_cpu(cpu_of(rq),
+ TICK_DEP_BIT_SCHED);
+ }
/*
* For now, let's refresh the load_avgs just when transitioning
--
2.55.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH 2/2] selftests/sched_ext: Verify nohz_full tick behavior
2026-07-06 16:18 [PATCHSET sched_ext/for-7.2-fixes] sched_ext: Fix finite-slice ticks on nohz_full Andrea Righi
2026-07-06 16:18 ` [PATCH 1/2] sched_ext: Enable tick for finite slices " Andrea Righi
@ 2026-07-06 16:18 ` Andrea Righi
1 sibling, 0 replies; 6+ messages in thread
From: Andrea Righi @ 2026-07-06 16:18 UTC (permalink / raw)
To: Tejun Heo, David Vernet, Changwoo Min
Cc: Joel Fernandes, sched-ext, linux-kernel
A finite-slice EXT task needs the periodic scheduler tick to expire its
slice even when nohz_full is enabled.
Add a regression test to validate the proper nohz_full behavior:
- the test selects a nohz_full CPU and first runs an EXT task with an
infinite slice,
- the task then is stopped, leaving the CPU idle with the tick stopped,
- then run an EXT task with a finite slice and verify that its
ops.tick() callback is invoked.
Skip the test when an allowed nohz_full CPU and a separate housekeeping
CPU are not available.
Signed-off-by: Andrea Righi <arighi@nvidia.com>
---
tools/testing/selftests/sched_ext/Makefile | 1 +
.../selftests/sched_ext/nohz_tick.bpf.c | 65 ++++
tools/testing/selftests/sched_ext/nohz_tick.c | 309 ++++++++++++++++++
3 files changed, 375 insertions(+)
create mode 100644 tools/testing/selftests/sched_ext/nohz_tick.bpf.c
create mode 100644 tools/testing/selftests/sched_ext/nohz_tick.c
diff --git a/tools/testing/selftests/sched_ext/Makefile b/tools/testing/selftests/sched_ext/Makefile
index 5d2dffca0e918..3cfe90e0f34fa 100644
--- a/tools/testing/selftests/sched_ext/Makefile
+++ b/tools/testing/selftests/sched_ext/Makefile
@@ -176,6 +176,7 @@ auto-test-targets := \
maybe_null \
minimal \
non_scx_kfunc_deny \
+ nohz_tick \
numa \
allowed_cpus \
peek_dsq \
diff --git a/tools/testing/selftests/sched_ext/nohz_tick.bpf.c b/tools/testing/selftests/sched_ext/nohz_tick.bpf.c
new file mode 100644
index 0000000000000..6998c5dd6bcb9
--- /dev/null
+++ b/tools/testing/selftests/sched_ext/nohz_tick.bpf.c
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES
+ *
+ * Exercise tick dependency transitions between infinite and finite slices.
+ */
+#include <scx/common.bpf.h>
+
+char _license[] SEC("license") = "GPL";
+
+const volatile s32 test_cpu;
+bool finite_phase;
+u64 nr_inf_running;
+u64 nr_finite_running;
+u64 nr_finite_ticks;
+
+UEI_DEFINE(uei);
+
+s32 BPF_STRUCT_OPS(nohz_tick_select_cpu, struct task_struct *p, s32 prev_cpu,
+ u64 wake_flags)
+{
+ return prev_cpu;
+}
+
+void BPF_STRUCT_OPS(nohz_tick_enqueue, struct task_struct *p, u64 enq_flags)
+{
+ u64 slice = finite_phase ? 1000000ULL : SCX_SLICE_INF;
+
+ scx_bpf_dsq_insert(p, SCX_DSQ_GLOBAL, slice, enq_flags);
+ if (enq_flags & SCX_ENQ_LAST)
+ scx_bpf_kick_cpu(test_cpu, SCX_KICK_IDLE);
+}
+
+void BPF_STRUCT_OPS(nohz_tick_running, struct task_struct *p)
+{
+ if (bpf_get_smp_processor_id() != test_cpu)
+ return;
+
+ if (finite_phase)
+ __sync_fetch_and_add(&nr_finite_running, 1);
+ else
+ __sync_fetch_and_add(&nr_inf_running, 1);
+}
+
+void BPF_STRUCT_OPS(nohz_tick_tick, struct task_struct *p)
+{
+ if (bpf_get_smp_processor_id() == test_cpu && finite_phase)
+ __sync_fetch_and_add(&nr_finite_ticks, 1);
+}
+
+void BPF_STRUCT_OPS(nohz_tick_exit, struct scx_exit_info *ei)
+{
+ UEI_RECORD(uei, ei);
+}
+
+SEC(".struct_ops.link")
+struct sched_ext_ops nohz_tick_ops = {
+ .select_cpu = (void *)nohz_tick_select_cpu,
+ .enqueue = (void *)nohz_tick_enqueue,
+ .running = (void *)nohz_tick_running,
+ .tick = (void *)nohz_tick_tick,
+ .exit = (void *)nohz_tick_exit,
+ .name = "nohz_tick",
+ .timeout_ms = 1000U,
+};
diff --git a/tools/testing/selftests/sched_ext/nohz_tick.c b/tools/testing/selftests/sched_ext/nohz_tick.c
new file mode 100644
index 0000000000000..5921bb9b9e83a
--- /dev/null
+++ b/tools/testing/selftests/sched_ext/nohz_tick.c
@@ -0,0 +1,309 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES
+ *
+ * Validate that a finite-slice EXT task restarts the scheduler tick when it
+ * follows an infinite-slice EXT task and an idle interval on a NOHZ_FULL CPU.
+ */
+#define _GNU_SOURCE
+
+#include <bpf/bpf.h>
+#include <errno.h>
+#include <sched.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <scx/common.h>
+
+#include "nohz_tick.bpf.skel.h"
+#include "scx_test.h"
+
+#ifndef SCHED_EXT
+#define SCHED_EXT 7
+#endif
+
+#define MIN_FINITE_TICKS 3
+#define PHASE_TIMEOUT_MS 1000
+
+struct nohz_tick_ctx {
+ struct nohz_tick *skel;
+ cpu_set_t original_mask;
+ int test_cpu;
+};
+
+static int first_allowed_cpu(const cpu_set_t *mask, int first, int last)
+{
+ int cpu;
+
+ for (cpu = first; cpu <= last && cpu < CPU_SETSIZE; cpu++)
+ if (CPU_ISSET(cpu, mask))
+ return cpu;
+
+ return -1;
+}
+
+static int find_nohz_full_cpu(const cpu_set_t *allowed)
+{
+ char buf[4096], *cur, *end;
+ FILE *file;
+
+ file = fopen("/sys/devices/system/cpu/nohz_full", "r");
+ if (!file)
+ return -1;
+ if (!fgets(buf, sizeof(buf), file)) {
+ fclose(file);
+ return -1;
+ }
+ fclose(file);
+
+ cur = buf;
+ while (*cur) {
+ long first, last;
+ int cpu;
+
+ while (*cur == ' ' || *cur == '\t' || *cur == ',')
+ cur++;
+ if (*cur < '0' || *cur > '9')
+ break;
+
+ errno = 0;
+ first = strtol(cur, &end, 10);
+ if (errno || end == cur || first < 0 || first >= CPU_SETSIZE)
+ return -1;
+ cur = end;
+ last = first;
+ if (*cur == '-') {
+ cur++;
+ errno = 0;
+ last = strtol(cur, &end, 10);
+ if (errno || end == cur || last < first)
+ return -1;
+ cur = end;
+ }
+
+ cpu = first_allowed_cpu(allowed, first, last);
+ if (cpu >= 0)
+ return cpu;
+ }
+
+ return -1;
+}
+
+static pid_t start_worker(int cpu)
+{
+ struct sched_param param = {};
+ cpu_set_t mask;
+ pid_t pid;
+
+ pid = fork();
+ if (pid != 0)
+ return pid;
+
+ /*
+ * Become EXT before touching the target so it stays idle until wakeup.
+ */
+ if (sched_setscheduler(0, SCHED_EXT, ¶m))
+ _exit(1);
+
+ CPU_ZERO(&mask);
+ CPU_SET(cpu, &mask);
+ if (sched_setaffinity(0, sizeof(mask), &mask))
+ _exit(1);
+
+ for (;;)
+ asm volatile("" ::: "memory");
+}
+
+static void stop_worker(pid_t pid)
+{
+ if (pid <= 0)
+ return;
+
+ kill(pid, SIGKILL);
+ waitpid(pid, NULL, 0);
+}
+
+static int pause_worker(pid_t pid)
+{
+ int status;
+
+ if (kill(pid, SIGSTOP))
+ return -errno;
+ if (waitpid(pid, &status, WUNTRACED) != pid)
+ return -errno;
+ if (!WIFSTOPPED(status))
+ return -ECHILD;
+
+ return 0;
+}
+
+static bool wait_for_counter(const u64 *counter, u64 value, int timeout_ms)
+{
+ int elapsed;
+
+ for (elapsed = 0; elapsed < timeout_ms; elapsed++) {
+ if (__atomic_load_n(counter, __ATOMIC_RELAXED) >= value)
+ return true;
+ usleep(1000);
+ }
+
+ return false;
+}
+
+static enum scx_test_status setup(void **ctx_ptr)
+{
+ struct nohz_tick_ctx *ctx;
+ cpu_set_t controller_mask;
+ int cpu;
+
+ ctx = calloc(1, sizeof(*ctx));
+ SCX_FAIL_IF(!ctx, "Failed to allocate context");
+ if (sched_getaffinity(0, sizeof(ctx->original_mask),
+ &ctx->original_mask)) {
+ free(ctx);
+ SCX_FAIL("Failed to get affinity (%d)", errno);
+ }
+
+ cpu = find_nohz_full_cpu(&ctx->original_mask);
+ if (cpu < 0) {
+ fprintf(stderr, "SKIP: no allowed NOHZ_FULL CPU\n");
+ free(ctx);
+ return SCX_TEST_SKIP;
+ }
+
+ controller_mask = ctx->original_mask;
+ CPU_CLR(cpu, &controller_mask);
+ if (CPU_COUNT(&controller_mask) == 0) {
+ fprintf(stderr, "SKIP: no housekeeping CPU available\n");
+ free(ctx);
+ return SCX_TEST_SKIP;
+ }
+
+ ctx->test_cpu = cpu;
+ ctx->skel = nohz_tick__open();
+ if (!ctx->skel) {
+ free(ctx);
+ SCX_FAIL("Failed to open skeleton");
+ }
+
+ SCX_ENUM_INIT(ctx->skel);
+ ctx->skel->rodata->test_cpu = cpu;
+ ctx->skel->struct_ops.nohz_tick_ops->flags |= SCX_OPS_SWITCH_PARTIAL |
+ SCX_OPS_ENQ_LAST;
+ if (nohz_tick__load(ctx->skel)) {
+ nohz_tick__destroy(ctx->skel);
+ free(ctx);
+ SCX_FAIL("Failed to load skeleton");
+ }
+
+ if (sched_setaffinity(0, sizeof(controller_mask), &controller_mask)) {
+ nohz_tick__destroy(ctx->skel);
+ free(ctx);
+ SCX_FAIL("Failed to move controller off CPU %d (%d)", cpu, errno);
+ }
+
+ *ctx_ptr = ctx;
+ return SCX_TEST_PASS;
+}
+
+static enum scx_test_status run(void *ctx_ptr)
+{
+ struct nohz_tick_ctx *ctx = ctx_ptr;
+ struct nohz_tick *skel = ctx->skel;
+ struct bpf_link *link = NULL;
+ enum scx_test_status status = SCX_TEST_FAIL;
+ pid_t finite_worker = -1;
+ pid_t inf_worker = -1;
+ int ret;
+
+ link = bpf_map__attach_struct_ops(skel->maps.nohz_tick_ops);
+ if (!link) {
+ SCX_ERR("Failed to attach scheduler");
+ goto out;
+ }
+
+ /*
+ * Establish SCX_RQ_CAN_STOP_TICK with an infinite-slice task.
+ */
+ inf_worker = start_worker(ctx->test_cpu);
+ if (inf_worker < 0) {
+ SCX_ERR("Failed to start infinite-slice worker (%d)", errno);
+ goto out;
+ }
+ if (!wait_for_counter(&skel->bss->nr_inf_running, 1,
+ PHASE_TIMEOUT_MS)) {
+ SCX_ERR("Infinite-slice worker was not scheduled");
+ goto out;
+ }
+
+ /* Block without exiting so the rq retains the infinite-slice state. */
+ ret = pause_worker(inf_worker);
+ if (ret) {
+ SCX_ERR("Failed to stop infinite-slice worker (%d)", ret);
+ goto out;
+ }
+
+ /* Let the target enter idle with its tick stopped. */
+ usleep(100000);
+
+ /*
+ * The next EXT task receives a finite slice and must restart the tick.
+ */
+ __atomic_store_n(&skel->bss->finite_phase, true, __ATOMIC_RELEASE);
+ finite_worker = start_worker(ctx->test_cpu);
+ if (finite_worker < 0) {
+ SCX_ERR("Failed to start finite-slice worker (%d)", errno);
+ goto out;
+ }
+ if (!wait_for_counter(&skel->bss->nr_finite_running, 1,
+ PHASE_TIMEOUT_MS)) {
+ SCX_ERR("Finite-slice worker was not scheduled");
+ goto out;
+ }
+ if (!wait_for_counter(&skel->bss->nr_finite_ticks, MIN_FINITE_TICKS,
+ PHASE_TIMEOUT_MS)) {
+ SCX_ERR("Finite-slice worker received only %llu scheduler ticks",
+ (unsigned long long)skel->bss->nr_finite_ticks);
+ goto out;
+ }
+
+ 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);
+ goto out;
+ }
+
+ fprintf(stderr, "CPU %d received %llu finite-slice ticks\n",
+ ctx->test_cpu,
+ (unsigned long long)skel->bss->nr_finite_ticks);
+ status = SCX_TEST_PASS;
+out:
+ stop_worker(finite_worker);
+ stop_worker(inf_worker);
+ if (link)
+ bpf_link__destroy(link);
+ return status;
+}
+
+static void cleanup(void *ctx_ptr)
+{
+ struct nohz_tick_ctx *ctx = ctx_ptr;
+
+ sched_setaffinity(0, sizeof(ctx->original_mask), &ctx->original_mask);
+ nohz_tick__destroy(ctx->skel);
+ free(ctx);
+}
+
+struct scx_test nohz_tick = {
+ .name = "nohz_tick",
+ .description = "Verify finite EXT slices restart the NOHZ_FULL tick",
+ .setup = setup,
+ .run = run,
+ .cleanup = cleanup,
+};
+REGISTER_SCX_TEST(&nohz_tick)
--
2.55.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH 1/2] sched_ext: Enable tick for finite slices on nohz_full
2026-07-06 16:18 ` [PATCH 1/2] sched_ext: Enable tick for finite slices " Andrea Righi
@ 2026-07-06 22:09 ` Tejun Heo
2026-07-07 5:52 ` Andrea Righi
0 siblings, 1 reply; 6+ messages in thread
From: Tejun Heo @ 2026-07-06 22:09 UTC (permalink / raw)
To: Andrea Righi, David Vernet, Changwoo Min
Cc: Joel Fernandes, Emil Tsalapatis, sched-ext, linux-kernel
Hello, Andrea.
Sashiko's first point looks real to me. The fix covers infinite->finite,
but a finite slice landing on a nohz_full CPU after idle has the same
issue whenever the previous run wasn't infinite (finite->idle->finite):
the enqueue path already cleared TICK_DEP_BIT_SCHED against the idle
rq->curr, and set_next_task_scx() re-asserts only on a slice-type
transition, so with SCX_RQ_CAN_STOP_TICK unchanged the tick stays
stopped.
set_next_task_scx() knows the incoming task, so it can assert directly
rather than keying off the transition, like sched_fair_update_stop_tick()
does for CFS. Moving the finite assertion out of the transition branch
should do it:
if (p->scx.slice != SCX_SLICE_INF && tick_nohz_full_cpu(cpu_of(rq)))
tick_nohz_dep_set_cpu(cpu_of(rq), TICK_DEP_BIT_SCHED);
Can you fold that in and add a finite->idle->finite selftest case for v2?
Thanks.
--
tejun
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH 1/2] sched_ext: Enable tick for finite slices on nohz_full
2026-07-06 22:09 ` Tejun Heo
@ 2026-07-07 5:52 ` Andrea Righi
0 siblings, 0 replies; 6+ messages in thread
From: Andrea Righi @ 2026-07-07 5:52 UTC (permalink / raw)
To: Tejun Heo
Cc: David Vernet, Changwoo Min, Joel Fernandes, Emil Tsalapatis,
sched-ext, linux-kernel
On Mon, Jul 06, 2026 at 12:09:36PM -1000, Tejun Heo wrote:
> Hello, Andrea.
>
> Sashiko's first point looks real to me. The fix covers infinite->finite,
> but a finite slice landing on a nohz_full CPU after idle has the same
> issue whenever the previous run wasn't infinite (finite->idle->finite):
> the enqueue path already cleared TICK_DEP_BIT_SCHED against the idle
> rq->curr, and set_next_task_scx() re-asserts only on a slice-type
> transition, so with SCX_RQ_CAN_STOP_TICK unchanged the tick stays
> stopped.
>
> set_next_task_scx() knows the incoming task, so it can assert directly
> rather than keying off the transition, like sched_fair_update_stop_tick()
> does for CFS. Moving the finite assertion out of the transition branch
> should do it:
>
> if (p->scx.slice != SCX_SLICE_INF && tick_nohz_full_cpu(cpu_of(rq)))
> tick_nohz_dep_set_cpu(cpu_of(rq), TICK_DEP_BIT_SCHED);
>
> Can you fold that in and add a finite->idle->finite selftest case for v2?
Yep, I'm currently looking/fixing at the issues found by Sashiko. I'll send a
v2 in a bit.
Thanks,
-Andrea
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH 1/2] sched_ext: Enable tick for finite slices on nohz_full
2026-07-07 8:17 [PATCHSET v2 sched_ext/for-7.2-fixes] sched_ext: Fix finite-slice ticks on nohz_full Andrea Righi
@ 2026-07-07 8:17 ` Andrea Righi
0 siblings, 0 replies; 6+ messages in thread
From: Andrea Righi @ 2026-07-07 8:17 UTC (permalink / raw)
To: Tejun Heo, David Vernet, Changwoo Min
Cc: Joel Fernandes, sched-ext, linux-kernel
set_next_task_scx() updates the tick dependency before __schedule()
updates rq->curr. When switching from a non-EXT task, such as idle, to
an EXT task with a finite slice, sched_update_tick_dependency() checks
the outgoing task and can allow the tick to remain stopped.
The dependency can also be lost without a slice-type transition. After a
finite-slice task leaves the CPU idle, the enqueue path can clear the
dependency against the idle rq->curr. SCX_RQ_CAN_STOP_TICK still records
a finite slice, so another finite task skips the transition block and
can run without the ticks needed to expire its slice.
The reverse mismatch can also happen when the last finite-slice EXT task
is dequeued: sub_nr_running() updates the dependency before rq->curr
changes, so the outgoing task state can keep the dependency set after
the CPU goes idle.
Fix this by unconditionally enabling the scheduler tick whenever a
finite-slice EXT task is selected on a nohz_full CPU. Moreover, when the
last runnable EXT task leaves, ignore the outgoing EXT slice state so
the generic scheduler can correctly re-evaluate and clear the tick
dependency.
Fixes: 22a920209ab6 ("sched_ext: Implement tickless support")
Signed-off-by: Andrea Righi <arighi@nvidia.com>
---
kernel/sched/ext/ext.c | 31 +++++++++++++++++++++++++++----
1 file changed, 27 insertions(+), 4 deletions(-)
diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c
index e75e2fd5ab7e3..b6a635ba269cc 100644
--- a/kernel/sched/ext/ext.c
+++ b/kernel/sched/ext/ext.c
@@ -2982,12 +2982,18 @@ static void set_next_task_scx(struct rq *rq, struct task_struct *p, bool first)
*/
if ((p->scx.slice == SCX_SLICE_INF) !=
(bool)(rq->scx.flags & SCX_RQ_CAN_STOP_TICK)) {
- if (p->scx.slice == SCX_SLICE_INF)
+ if (p->scx.slice == SCX_SLICE_INF) {
+ /*
+ * Bypass mode always assigns finite slices, so @p
+ * can't have an infinite slice while bypassing.
+ * Therefore, sched_update_tick_dependency() can safely
+ * evaluate the outgoing task.
+ */
rq->scx.flags |= SCX_RQ_CAN_STOP_TICK;
- else
+ sched_update_tick_dependency(rq);
+ } else {
rq->scx.flags &= ~SCX_RQ_CAN_STOP_TICK;
-
- sched_update_tick_dependency(rq);
+ }
/*
* For now, let's refresh the load_avgs just when transitioning
@@ -2997,6 +3003,14 @@ static void set_next_task_scx(struct rq *rq, struct task_struct *p, bool first)
*/
update_other_load_avgs(rq);
}
+
+ /*
+ * @rq still references the outgoing scheduling context. A finite slice
+ * is sufficient by itself to require the tick.
+ */
+ if (p->scx.slice != SCX_SLICE_INF &&
+ tick_nohz_full_cpu(cpu_of(rq)))
+ tick_nohz_dep_set_cpu(cpu_of(rq), TICK_DEP_BIT_SCHED);
}
static enum scx_cpu_preempt_reason
@@ -4321,6 +4335,15 @@ bool scx_can_stop_tick(struct rq *rq)
if (p->sched_class != &ext_sched_class)
return true;
+ /*
+ * @rq->curr may still reference an outgoing EXT task after it has been
+ * dequeued. If no EXT tasks are accounted on @rq, ignore its stale
+ * slice state. If another task is dispatched from a DSQ,
+ * set_next_task_scx() will update the dependency for the incoming task.
+ */
+ if (!rq->scx.nr_running)
+ return true;
+
if (scx_bypassing(sch, cpu_of(rq)))
return false;
--
2.55.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-07-07 8:26 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-06 16:18 [PATCHSET sched_ext/for-7.2-fixes] sched_ext: Fix finite-slice ticks on nohz_full Andrea Righi
2026-07-06 16:18 ` [PATCH 1/2] sched_ext: Enable tick for finite slices " Andrea Righi
2026-07-06 22:09 ` Tejun Heo
2026-07-07 5:52 ` Andrea Righi
2026-07-06 16:18 ` [PATCH 2/2] selftests/sched_ext: Verify nohz_full tick behavior Andrea Righi
-- strict thread matches above, loose matches on Subject: below --
2026-07-07 8:17 [PATCHSET v2 sched_ext/for-7.2-fixes] sched_ext: Fix finite-slice ticks on nohz_full Andrea Righi
2026-07-07 8:17 ` [PATCH 1/2] sched_ext: Enable tick for finite slices " Andrea Righi
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox