FAILED: patch "[PATCH] sched_ext: Generate qseq from a per-task counter" failed to apply to 7.2-stable tree
Subsystems (3)
[email protected]
maintainerThe patch below does not apply to the 7.2-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-7.2.y
git checkout FETCH_HEAD
git cherry-pick -x e6ac89b8b1c12e9104df45a14a26e2dfa96ded06
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026101211-seismic-mutable-0582@gregkh' --subject-prefix 'PATCH 7.2.y' 'HEAD^..'
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From e6ac89b8b1c12e9104df45a14a26e2dfa96ded06 Mon Sep 17 00:00:00 2001
From: Kuba Piecuch <[email protected]>
Date: Sat, 3 Oct 2026 11:53:17 +0000
Subject: [PATCH] sched_ext: Generate qseq from a per-task counter
finish_dispatch() uses the qseq embedded in p->scx.ops_state to tell whether the QUEUED instance of a task it's about to claim is the one scx_bpf_dsq_insert() saw. qseq is generated from rq->scx.ops_qseq, but the counters of different rqs are independent, so if a task is dequeued and re-enqueued on a different rq between scx_bpf_dsq_insert() and finish_dispatch(), the new QUEUED instance can end up with the same qseq as the old one:
CPU X CPU Z
----- -----
enqueue p on rq A, qseq = N
ops.dispatch()
scx_bpf_dsq_insert(p)
records qseq N
sched_setaffinity(p)
dequeue p from rq A enqueue p on rq B, qseq = N
finish_dispatch(p, N)
qseq matches, p is claimed
The claim itself is still atomic so the core stays consistent, but an insert issued for a previous QUEUED instance gets applied to a new one which the BPF scheduler has just received through ops.enqueue(). This breaks the guarantee that dispatches targeting a stale instance are ignored.
Generate qseq from a per-task counter, p->scx.ops_qseq, instead so that consecutive QUEUED instances of a task never share a qseq regardless of which rq they're on. The counter is only updated in scx_do_enqueue_task() with the task's rq locked, so no additional synchronization is needed, and it fits in an existing hole in struct sched_ext_entity on 64bit. Remove the now unused rq->scx.ops_qseq.
Never generate qseq 0. NONE and DISPATCHING don't carry a qseq, so scx_bpf_dsq_insert() on a task in either state records 0. With a per-task counter, every task's first QUEUED instance would otherwise get qseq 0 and could be claimed by such an insert. Wrap the counter where the QSEQ field wraps so that it can't reach a value that shifts to 0 on 32bit either.
include/linux/sched/ext.h +1 -0
@@ -207,6 +207,7 @@ struct sched_ext_entity {s32 holding_cpu;s32 selected_cpu;s32 runnable_cpu;/* cpu @p is runnable on, -1 if not */ +u32 ops_qseq;/* protected by rq lock */ struct task_struct *kf_tasks[2 ];/* see SCX_CALL_OP_TASK() */ struct list_head runnable_node;/* rq->scx.runnable_list */
kernel/sched/ext/ext.c +10 -4
@@ -2057,8 +2057,14 @@ void scx_do_enqueue_task(struct rq *rq, struct task_struct *p, u64 enq_flags,if (unlikely (!SCX_HAS_OP (sch, enqueue))) goto global; -/* DSQ bypass didn't trigger, enqueue on the BPF scheduler */ - qseq = rq->scx.ops_qseq++ <<SCX_OPSS_QSEQ_SHIFT ; +/* +* DSQ bypass didn't trigger, enqueue on the BPF scheduler. Wrap the +* per-task qseq counter where the QSEQ field wraps and skip 0, which is +* what scx_bpf_dsq_insert() records for a task in NONE or DISPATCHING. +*/ + p->scx.ops_qseq = ((p->scx.ops_qseq +1 ) & + (SCX_OPSS_QSEQ_MASK >>SCX_OPSS_QSEQ_SHIFT )) ?:1 ; + qseq = (unsigned long )p->scx.ops_qseq <<SCX_OPSS_QSEQ_SHIFT ;WARN_ON_ONCE (atomic_long_read (&p->scx.ops_state) !=SCX_OPSS_NONE );atomic_long_set (&p->scx.ops_state,SCX_OPSS_QUEUEING | qseq);
@@ -6969,9 +6975,9 @@ static void scx_dump_cpu(struct scx_sched *sch, struct seq_buf *s,seq_buf_init (&ns, buf, avail);dump_newline (&ns); -scx_dump_line (&ns,"CPU %-4d: nr_run=%u flags=0x%x cpu_rel=%d ops_qseq=%lu ksync=%lu" , +scx_dump_line (&ns,"CPU %-4d: nr_run=%u flags=0x%x cpu_rel=%d ksync=%lu" , cpu, rq->scx.nr_running, rq->scx.flags, rq->scx.cpu_released, - rq->scx.ops_qseq, rq->scx.kick_sync); + rq->scx.kick_sync);scx_rescue_dump (&ns, rq);scx_dump_line (&ns," curr=%s[%d] class=%ps" , rq->curr->comm, rq->curr->pid, rq->curr->sched_class);
kernel/sched/ext/internal.h +5 -0
@@ -1998,6 +1998,11 @@ enum scx_ops_state {* dequeue/requeue, the dispatcher can tell whether it still has a claim * on the task being dispatched. * +* QSEQ is generated from the per-task p->scx.ops_qseq counter so that +* it doesn't repeat across QUEUED instances of the same task even if +* the task moves between rqs. 0 is never used as a valid QSEQ since +* NONE and DISPATCHING map to this value. +* * As some 32bit archs can't do 64bit store_release/load_acquire, * p->scx.ops_state is atomic_long_t which leaves 30 bits for QSEQ on * 32bit machines. The dispatch race window QSEQ protects is very narrow
kernel/sched/sched.h +0 -1
@@ -813,7 +813,6 @@ struct scx_rq {#endif struct list_head runnable_list;/* runnable tasks on this rq */ struct list_head ddsp_deferred_locals;/* deferred ddsps from enq */ -unsigned long ops_qseq;/* both stashed across the activate_task() in move_remote_task_to_local_dsq() */ u64 remote_activate_enq_flags;struct scx_sched *remote_activate_sch;
PATCH 1/1 sched_ext: Generate qseq from a per-task counter
finish_dispatch() uses the qseq embedded in p->scx.ops_state to tell whether the QUEUED instance of a task it's about to claim is the one scx_bpf_dsq_insert() saw. qseq is generated from rq->scx.ops_qseq, but the counters of different rqs are independent, so if a task is dequeued and re-enqueued on a different rq between scx_bpf_dsq_insert() and finish_dispatch(), the new QUEUED instance can end up with the same qseq as the old one:
CPU X CPU Z
----- -----
enqueue p on rq A, qseq = N
ops.dispatch()
scx_bpf_dsq_insert(p)
records qseq N
sched_setaffinity(p)
dequeue p from rq A enqueue p on rq B, qseq = N
finish_dispatch(p, N)
qseq matches, p is claimed
The claim itself is still atomic so the core stays consistent, but an insert issued for a previous QUEUED instance gets applied to a new one which the BPF scheduler has just received through ops.enqueue(). This breaks the guarantee that dispatches targeting a stale instance are ignored.
Generate qseq from a per-task counter, p->scx.ops_qseq, instead so that consecutive QUEUED instances of a task never share a qseq regardless of which rq they're on. The counter is only updated in scx_do_enqueue_task() with the task's rq locked, so no additional synchronization is needed, and it fits in an existing hole in struct sched_ext_entity on 64bit. Remove the now unused rq->scx.ops_qseq.
Never generate qseq 0. NONE and DISPATCHING don't carry a qseq, so scx_bpf_dsq_insert() on a task in either state records 0. With a per-task counter, every task's first QUEUED instance would otherwise get qseq 0 and could be claimed by such an insert. Wrap the counter where the QSEQ field wraps so that it can't reach a value that shifts to 0 on 32bit either.
[ jpiecuch: 7.2.y lacks runnable_cpu in sched_ext_entity, so ops_qseq goes
after selected_cpu; adjust context in scx_rq and the dump_line() CPU line ]
--- include/linux/sched/ext.h | 1 + kernel/sched/ext/ext.c | 15 ++++++++++----- kernel/sched/ext/internal.h | 5 +++++ kernel/sched/sched.h | 1 - 4 files changed, 16 insertions(+), 6 deletions(-)
include/linux/sched/ext.h +1 -0
@@ -198,6 +198,7 @@ struct sched_ext_entity {s32 sticky_cpu;s32 holding_cpu;s32 selected_cpu; +u32 ops_qseq;/* protected by rq lock */ struct task_struct *kf_tasks[2 ];/* see SCX_CALL_OP_TASK() */ struct list_head runnable_node;/* rq->scx.runnable_list */
kernel/sched/ext/ext.c +10 -5
@@ -1953,8 +1953,14 @@ static void do_enqueue_task(struct rq *rq, struct task_struct *p, u64 enq_flags,if (unlikely (!SCX_HAS_OP (sch, enqueue))) goto global; -/* DSQ bypass didn't trigger, enqueue on the BPF scheduler */ - qseq = rq->scx.ops_qseq++ <<SCX_OPSS_QSEQ_SHIFT ; +/* +* DSQ bypass didn't trigger, enqueue on the BPF scheduler. Wrap the +* per-task qseq counter where the QSEQ field wraps and skip 0, which is +* what scx_bpf_dsq_insert() records for a task in NONE or DISPATCHING. +*/ + p->scx.ops_qseq = ((p->scx.ops_qseq +1 ) & + (SCX_OPSS_QSEQ_MASK >>SCX_OPSS_QSEQ_SHIFT )) ?:1 ; + qseq = (unsigned long )p->scx.ops_qseq <<SCX_OPSS_QSEQ_SHIFT ;WARN_ON_ONCE (atomic_long_read (&p->scx.ops_state) !=SCX_OPSS_NONE );atomic_long_set (&p->scx.ops_state,SCX_OPSS_QUEUEING | qseq);
@@ -6690,10 +6696,9 @@ static void scx_dump_cpu(struct scx_sched *sch, struct seq_buf *s,seq_buf_init (&ns, buf, avail);dump_newline (&ns); -dump_line (&ns,"CPU %-4d: nr_run=%u flags=0x%x cpu_rel=%d ops_qseq=%lu ksync=%lu" , +dump_line (&ns,"CPU %-4d: nr_run=%u flags=0x%x cpu_rel=%d ksync=%lu" , cpu, rq->scx.nr_running, rq->scx.flags, - rq->scx.cpu_released, rq->scx.ops_qseq, - rq->scx.kick_sync); + rq->scx.cpu_released, rq->scx.kick_sync);dump_line (&ns," curr=%s[%d] class=%ps" , rq->curr->comm, rq->curr->pid, rq->curr->sched_class);
kernel/sched/ext/internal.h +5 -0
@@ -1508,6 +1508,11 @@ enum scx_ops_state {* dequeue/requeue, the dispatcher can tell whether it still has a claim * on the task being dispatched. * +* QSEQ is generated from the per-task p->scx.ops_qseq counter so that +* it doesn't repeat across QUEUED instances of the same task even if +* the task moves between rqs. 0 is never used as a valid QSEQ since +* NONE and DISPATCHING map to this value. +* * As some 32bit archs can't do 64bit store_release/load_acquire, * p->scx.ops_state is atomic_long_t which leaves 30 bits for QSEQ on * 32bit machines. The dispatch race window QSEQ protects is very narrow
kernel/sched/sched.h +0 -1
@@ -795,7 +795,6 @@ struct scx_rq {struct scx_dispatch_q local_dsq;struct list_head runnable_list;/* runnable tasks on this rq */ struct list_head ddsp_deferred_locals;/* deferred ddsps from enq */ -unsigned long ops_qseq;u64 extra_enq_flags;/* see move_task_to_local_dsq() */ u32 nr_running;u32 cpuperf_target;/* [0, SCHED_CAPACITY_SCALE] */
Signature · 1 line
2.56.0.385.gd3acb90ef8-goog