Re: [PATCH sched_ext/for-7.1] sched_ext: Documentation: Add missing calls to quiescent(), runnable()

From: Kuba Piecuch

Date: Thu Apr 09 2026 - 09:32:07 EST


On Thu Apr 9, 2026 at 9:46 AM UTC, Christian Loehle wrote:
...
>>>
>>> ops.init_task(); /* A new task is created */
>>> ops.enable(); /* Enable BPF scheduling for the task */
>>>
>>> while (task in SCHED_EXT) {
>>> if (task can migrate)
>>> ops.select_cpu(); /* Called on wakeup (optimization) */
>>>
>>> ops.runnable(); /* Task becomes ready to run */
>>>
>>> while (task_is_runnable(task)) {
>>> if (task is not in a DSQ || task->scx.slice == 0) {
>>> ops.enqueue(); /* Task can be added to a DSQ */
>>>
>>> /* Task property change (i.e., affinity, nice, etc.)? */
>>> if (sched_change(task)) {
>>> ops.dequeue(); /* Exiting BPF scheduler custody */
>>> ops.quiescent();
>>>
>>> /* Property change callback, e.g. ops.set_weight() */
>>>
>>> ops.runnable();
>>> continue;
>>> }
>>>
>>> /* Any usable CPU becomes available */
>>>
>>> ops.dispatch(); /* Task is moved to a local DSQ */
>>> ops.dequeue(); /* Exiting BPF scheduler custody */
> Is this true here? Any dispatch followed by a dequeue?

The comment next to ops.dispatch() says the task is moved to a local DSQ,
so if we assume that, then I think it will always be followed by ops.dequeue().
Same if we move the task to the global DSQ.

Of course, you could do something weird like dispatch the task to a user DSQ,
in which case there won't be a dequeue and the task won't start running, but
that's weird enough that I don't think we need to consider it.

You could also have a property change racing with the dispatch which would make
the dispatch fail and not be followed by a dequeue, but again, we need to draw
the line somewhere.

So, in other words, any _successful_ dispatch to a _terminal_ DSQ is always
followed by a dequeue.

Another case that isn't handled here is direct dispatch to a terminal DSQ from
ops.enqueue(), where we don't get ops.dispatch() or ops.dequeue() and go
straight to ops.running(). If any of the above cases should be handled in the
pseudocode, I'd say it's this one.

>>> }
>>>
>>> ops.running(); /* Task starts running on its assigned CPU */
>>>
>>> while (task_is_runnable(task) && task->scx.slice > 0) {
>>> ops.tick(); /* Called every 1/HZ seconds */
>>>
>>> if (task->scx.slice == 0)
>>> ops.dispatch(); /* task->scx.slice can be refilled */
>>> }
>>>
>>> ops.stopping(); /* Task stops running (time slice expires or wait) */
>>> }
>>>
>>> ops.quiescent(); /* Task releases its assigned CPU (wait) */
>>> }
>>>
>>> ops.disable(); /* Disable BPF scheduling for the task */
>>> ops.exit_task(); /* Task is destroyed */