Sched_ext development
 help / color / mirror / Atom feed
* [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; 9+ 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] 9+ 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 16:49   ` sashiko-bot
  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, 2 replies; 9+ 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] 9+ 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
  2026-07-06 16:37   ` sashiko-bot
  1 sibling, 1 reply; 9+ 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, &param))
+		_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] 9+ messages in thread

* Re: [PATCH 2/2] selftests/sched_ext: Verify nohz_full tick behavior
  2026-07-06 16:18 ` [PATCH 2/2] selftests/sched_ext: Verify nohz_full tick behavior Andrea Righi
@ 2026-07-06 16:37   ` sashiko-bot
  0 siblings, 0 replies; 9+ messages in thread
From: sashiko-bot @ 2026-07-06 16:37 UTC (permalink / raw)
  To: Andrea Righi; +Cc: sched-ext

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Worker processes will spin indefinitely if the test terminates abnormally.
--

commit 5f5f7d1cf8a2f7b2edb91658dc637840224ea1dd
Author: Andrea Righi <arighi@nvidia.com>

selftests/sched_ext: Verify nohz_full tick behavior

This commit adds a regression test to validate proper nohz_full behavior in
sched_ext. It verifies that a finite-slice EXT task restarts the periodic
scheduler tick when it follows an infinite-slice task on a NOHZ_FULL CPU.

> diff --git a/tools/testing/selftests/sched_ext/nohz_tick.c b/tools/testing/selftests/sched_ext/nohz_tick.c
> new file mode 100644
> --- /dev/null
> +++ b/tools/testing/selftests/sched_ext/nohz_tick.c

[ ... ]

> +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, &param))
> +		_exit(1);
> +
> +	CPU_ZERO(&mask);
> +	CPU_SET(cpu, &mask);
> +	if (sched_setaffinity(0, sizeof(mask), &mask))
> +		_exit(1);
> +
> +	for (;;)
> +		asm volatile("" ::: "memory");

[Severity: Medium]
If the test runner terminates abnormally before reaching stop_worker(), will
this orphaned child process continue to spin indefinitely?

Since the child runs under the SCHED_EXT policy and is pinned to a specific
CPU, an orphaned child would consume 100% of that CPU indefinitely and
potentially interfere with subsequent tests.

Would it be safer to use prctl(PR_SET_PDEATHSIG, SIGKILL) after forking so
the worker is automatically killed if the parent test process exits
unexpectedly?

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260706162819.650155-1-arighi@nvidia.com?part=2

^ permalink raw reply	[flat|nested] 9+ 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 16:49   ` sashiko-bot
  2026-07-06 22:09   ` Tejun Heo
  1 sibling, 0 replies; 9+ messages in thread
From: sashiko-bot @ 2026-07-06 16:49 UTC (permalink / raw)
  To: Andrea Righi; +Cc: sched-ext

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:

New issues:
- [High] The patch's fix for finite slices is incomplete: it fails to restore the tick dependency when switching from a tick-stopped non-EXT task (such as idle) to an EXT task with a finite slice, if the last EXT task on that CPU also had a finite slice.

Pre-existing issues:
- [High] The patch manually sets the tick dependency when transitioning to a finite-slice SCX task but fails to ensure it is cleared when transitioning out to a non-EXT task (such as CFS or idle), causing a persistent leak that breaks nohz_full isolation.
- [High] When transitioning to an infinite-slice task, the patch continues to call `sched_update_tick_dependency(rq)`. Because `scx_can_stop_tick()` evaluates the outgoing task (`rq->curr`), transitioning from a non-EXT task to an infinite-slice EXT task while in bypass mode will incorrectly clear the tick dependency.
--

commit ed44446cf6110ce75b933d2ec6430b4cf1e80f2a
Author: Andrea Righi <arighi@nvidia.com>

sched_ext: Enable tick for finite slices on nohz_full

This commit addresses an issue where an incoming SCHED_EXT task with a finite
slice could fail to receive the ticks needed to expire its slice. It changes
set_next_task_scx() to directly set the TICK_DEP_BIT_SCHED dependency for
finite-slice tasks, bypassing the normal sched_update_tick_dependency() check
that relies on the outgoing task state. For infinite-slice tasks, it continues
to use sched_update_tick_dependency().

> diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c
> index 691d53fe0f648..9b2399a1e9656 100644
> --- a/kernel/sched/ext/ext.c
> +++ b/kernel/sched/ext/ext.c
> @@ -2974,12 +2974,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)) {

[Severity: High]
Does this condition prevent the tick dependency from being restored when
switching from a tick-stopped non-EXT task (such as idle) to an EXT task
with a finite slice, if the last EXT task on that CPU also had a finite slice?

If a finite-slice EXT task previously cleared SCX_RQ_CAN_STOP_TICK, and the
CPU then transitions to a non-EXT state that clears the global tick dependency,
the false != false evaluation here will bypass this entire block. This would
cause the new finite-slice EXT task to run without a tick, potentially causing
it to monopolize the CPU since its slice won't expire.

> -		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);

[Severity: High]
This isn't a bug introduced by this patch, but when transitioning from a
non-EXT task to an infinite-slice EXT task while in bypass mode, could calling
sched_update_tick_dependency() here incorrectly clear the tick dependency?

Because scx_can_stop_tick() evaluates the outgoing task:

kernel/sched/ext/ext.c:scx_can_stop_tick() {
    ...
	if (p->sched_class != &ext_sched_class)
		return true;
    ...
}

It will return true early for the outgoing non-EXT task. This bypasses the
scx_bypassing() check entirely, which could stall tasks queued in the bypass
DSQ indefinitely by incorrectly stopping the scheduler tick.

> +		} 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);

[Severity: High]
This is a pre-existing issue, but does setting the tick dependency here leave a
persistent leak when the task exits the SCX class?

When an EXT task with a finite slice blocks or exits on a nohz_full CPU,
dequeue_task() calls sub_nr_running(), which invokes
sched_update_tick_dependency(). At that moment, __schedule() hasn't updated
rq->curr, so scx_can_stop_tick() evaluates the outgoing finite-slice EXT
task and returns false.

This leaves the tick dependency set. Subsequent transitions to non-EXT tasks
like idle or CFS do not re-evaluate or clear this dependency, meaning the tick
could run persistently on a nohz_full CPU, breaking isolation.

> +		}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260706162819.650155-1-arighi@nvidia.com?part=1

^ permalink raw reply	[flat|nested] 9+ 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 16:49   ` sashiko-bot
@ 2026-07-06 22:09   ` Tejun Heo
  2026-07-07  5:52     ` Andrea Righi
  1 sibling, 1 reply; 9+ 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] 9+ 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; 9+ 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] 9+ 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
  2026-07-07 20:56   ` Tejun Heo
  0 siblings, 1 reply; 9+ 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] 9+ messages in thread

* Re: [PATCH 1/2] sched_ext: Enable tick for finite slices on nohz_full
  2026-07-07  8:17 ` [PATCH 1/2] sched_ext: Enable tick for finite slices " Andrea Righi
@ 2026-07-07 20:56   ` Tejun Heo
  0 siblings, 0 replies; 9+ messages in thread
From: Tejun Heo @ 2026-07-07 20:56 UTC (permalink / raw)
  To: Andrea Righi
  Cc: David Vernet, Changwoo Min, Joel Fernandes, Emil Tsalapatis,
	sched-ext, linux-kernel

Hello, Andrea.

On Tue, Jul 07, 2026 at 10:17:59AM +0200, Andrea Righi wrote:
> 	if ((p->scx.slice == SCX_SLICE_INF) !=
> 	    (bool)(rq->scx.flags & SCX_RQ_CAN_STOP_TICK)) {
> 		if (p->scx.slice == SCX_SLICE_INF) {

The slice type is tested three times here (the XOR, the inner if, and the
finite tail). Can you split on it once instead?

	if (p->scx.slice == SCX_SLICE_INF) {
		if (!(rq->scx.flags & SCX_RQ_CAN_STOP_TICK)) {
			rq->scx.flags |= SCX_RQ_CAN_STOP_TICK;
			sched_update_tick_dependency(rq);
			update_other_load_avgs(rq);
		}
	} else {
		if (rq->scx.flags & SCX_RQ_CAN_STOP_TICK) {
			rq->scx.flags &= ~SCX_RQ_CAN_STOP_TICK;
			update_other_load_avgs(rq);
		}
		if (tick_nohz_full_cpu(cpu_of(rq)))
			tick_nohz_dep_set_cpu(cpu_of(rq), TICK_DEP_BIT_SCHED);
	}

update_other_load_avgs() lands in both arms, but the infinite/finite split
reads more directly than keying off the transition.

Thanks.

-- 
tejun

^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2026-07-07 20:56 UTC | newest]

Thread overview: 9+ 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 16:49   ` sashiko-bot
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
2026-07-06 16:37   ` sashiko-bot
  -- 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
2026-07-07 20:56   ` Tejun Heo

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox