* [RFC PATCH] sched/deadline: Avoid dl_server boosting with expired deadline
@ 2025-10-07 12:29 Gabriele Monaco
2025-10-14 9:30 ` Gabriele Monaco
2025-10-14 9:54 ` Peter Zijlstra
0 siblings, 2 replies; 26+ messages in thread
From: Gabriele Monaco @ 2025-10-07 12:29 UTC (permalink / raw)
To: linux-kernel, Juri Lelli, Ingo Molnar, Peter Zijlstra
Cc: Gabriele Monaco, Clark Williams
Recent changes to the deadline server leave it running when the system
is idle. If the system is idle for longer than the dl_server period and
the first scheduling occurs after a fair task wakes up, the algorithm
picks the server as the earliest deadline (in the past) and that boosts
the fair task that just woke up while:
* the deadline is in the past
* the server consumed all its runtime (in background)
* there is no starvation (idle for about a period)
Prevent the server from boosting a task when the deadline is in the
past. Instead, replenish a new period and start the server as deferred.
Fixes: 4ae8d9aa9f9d ("sched/deadline: Fix dl_server getting stuck")
To: Juri Lelli <juri.lelli@redhat.com>
Cc: Clark Williams <williams@redhat.com>
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
This behaviour was observed using the RV monitors in [1] and the patch
was validated on an adapted version of the models. The models are not
exhaustively validating the dl_server behaviour.
[1] - https://lore.kernel.org/lkml/20250919140954.104920-21-gmonaco@redhat.com
kernel/sched/deadline.c | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c
index 72c1f72463c7..b3e3d506a18d 100644
--- a/kernel/sched/deadline.c
+++ b/kernel/sched/deadline.c
@@ -2371,6 +2371,17 @@ static struct task_struct *__pick_task_dl(struct rq *rq)
dl_server_stop(dl_se);
goto again;
}
+ /*
+ * If the CPU was idle for long enough time and wakes up
+ * because of a fair task, the dl_server may run after its
+ * period elapsed. Replenish a new period as deferred, since we
+ * are clearly not handling starvation here.
+ */
+ if (dl_time_before(dl_se->deadline, rq_clock(rq))) {
+ dl_se->dl_defer_running = 0;
+ replenish_dl_new_period(dl_se, rq);
+ goto again;
+ }
rq->dl_server = dl_se;
} else {
p = dl_task_of(dl_se);
base-commit: e5f0a698b34ed76002dc5cff3804a61c80233a7a
--
2.51.0
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [RFC PATCH] sched/deadline: Avoid dl_server boosting with expired deadline
2025-10-07 12:29 [RFC PATCH] sched/deadline: Avoid dl_server boosting with expired deadline Gabriele Monaco
@ 2025-10-14 9:30 ` Gabriele Monaco
2025-10-14 9:54 ` Peter Zijlstra
1 sibling, 0 replies; 26+ messages in thread
From: Gabriele Monaco @ 2025-10-14 9:30 UTC (permalink / raw)
To: linux-kernel, Juri Lelli, Ingo Molnar, Peter Zijlstra; +Cc: Clark Williams
On Tue, 2025-10-07 at 14:29 +0200, Gabriele Monaco wrote:
> Recent changes to the deadline server leave it running when the system
> is idle. If the system is idle for longer than the dl_server period and
> the first scheduling occurs after a fair task wakes up, the algorithm
> picks the server as the earliest deadline (in the past) and that boosts
> the fair task that just woke up while:
> * the deadline is in the past
> * the server consumed all its runtime (in background)
> * there is no starvation (idle for about a period)
>
> Prevent the server from boosting a task when the deadline is in the
> past. Instead, replenish a new period and start the server as deferred.
Peter, Juri, what do you think about this change and "issue" with the server
boosting after idle?
Thanks,
Gabriele
>
> Fixes: 4ae8d9aa9f9d ("sched/deadline: Fix dl_server getting stuck")
> To: Juri Lelli <juri.lelli@redhat.com>
> Cc: Clark Williams <williams@redhat.com>
> Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
> ---
>
> This behaviour was observed using the RV monitors in [1] and the patch
> was validated on an adapted version of the models. The models are not
> exhaustively validating the dl_server behaviour.
>
> [1] - https://lore.kernel.org/lkml/20250919140954.104920-21-gmonaco@redhat.com
>
> kernel/sched/deadline.c | 11 +++++++++++
> 1 file changed, 11 insertions(+)
>
> diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c
> index 72c1f72463c7..b3e3d506a18d 100644
> --- a/kernel/sched/deadline.c
> +++ b/kernel/sched/deadline.c
> @@ -2371,6 +2371,17 @@ static struct task_struct *__pick_task_dl(struct rq
> *rq)
> dl_server_stop(dl_se);
> goto again;
> }
> + /*
> + * If the CPU was idle for long enough time and wakes up
> + * because of a fair task, the dl_server may run after its
> + * period elapsed. Replenish a new period as deferred, since we
> + * are clearly not handling starvation here.
> + */
> + if (dl_time_before(dl_se->deadline, rq_clock(rq))) {
> + dl_se->dl_defer_running = 0;
> + replenish_dl_new_period(dl_se, rq);
> + goto again;
> + }
> rq->dl_server = dl_se;
> } else {
> p = dl_task_of(dl_se);
>
> base-commit: e5f0a698b34ed76002dc5cff3804a61c80233a7a
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [RFC PATCH] sched/deadline: Avoid dl_server boosting with expired deadline
2025-10-07 12:29 [RFC PATCH] sched/deadline: Avoid dl_server boosting with expired deadline Gabriele Monaco
2025-10-14 9:30 ` Gabriele Monaco
@ 2025-10-14 9:54 ` Peter Zijlstra
2025-10-14 10:05 ` Gabriele Monaco
1 sibling, 1 reply; 26+ messages in thread
From: Peter Zijlstra @ 2025-10-14 9:54 UTC (permalink / raw)
To: Gabriele Monaco; +Cc: linux-kernel, Juri Lelli, Ingo Molnar, Clark Williams
On Tue, Oct 07, 2025 at 02:29:04PM +0200, Gabriele Monaco wrote:
> Recent changes to the deadline server leave it running when the system
> is idle. If the system is idle for longer than the dl_server period and
> the first scheduling occurs after a fair task wakes up, the algorithm
> picks the server as the earliest deadline (in the past) and that boosts
> the fair task that just woke up while:
> * the deadline is in the past
> * the server consumed all its runtime (in background)
> * there is no starvation (idle for about a period)
>
> Prevent the server from boosting a task when the deadline is in the
> past. Instead, replenish a new period and start the server as deferred.
>
> Fixes: 4ae8d9aa9f9d ("sched/deadline: Fix dl_server getting stuck")
> To: Juri Lelli <juri.lelli@redhat.com>
> Cc: Clark Williams <williams@redhat.com>
> Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
> ---
>
> This behaviour was observed using the RV monitors in [1] and the patch
> was validated on an adapted version of the models. The models are not
> exhaustively validating the dl_server behaviour.
>
> [1] - https://lore.kernel.org/lkml/20250919140954.104920-21-gmonaco@redhat.com
>
> kernel/sched/deadline.c | 11 +++++++++++
> 1 file changed, 11 insertions(+)
>
> diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c
> index 72c1f72463c7..b3e3d506a18d 100644
> --- a/kernel/sched/deadline.c
> +++ b/kernel/sched/deadline.c
> @@ -2371,6 +2371,17 @@ static struct task_struct *__pick_task_dl(struct rq *rq)
> dl_server_stop(dl_se);
> goto again;
> }
> + /*
> + * If the CPU was idle for long enough time and wakes up
> + * because of a fair task, the dl_server may run after its
> + * period elapsed. Replenish a new period as deferred, since we
> + * are clearly not handling starvation here.
> + */
> + if (dl_time_before(dl_se->deadline, rq_clock(rq))) {
> + dl_se->dl_defer_running = 0;
> + replenish_dl_new_period(dl_se, rq);
> + goto again;
> + }
> rq->dl_server = dl_se;
> } else {
> p = dl_task_of(dl_se);
>
I'm a bit confused, should not enqueue ensure deadline is in the future?
And if it doesn't shouldn't we fix the enqueue path somewhere?
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [RFC PATCH] sched/deadline: Avoid dl_server boosting with expired deadline
2025-10-14 9:54 ` Peter Zijlstra
@ 2025-10-14 10:05 ` Gabriele Monaco
2025-10-14 10:25 ` Peter Zijlstra
0 siblings, 1 reply; 26+ messages in thread
From: Gabriele Monaco @ 2025-10-14 10:05 UTC (permalink / raw)
To: Peter Zijlstra; +Cc: linux-kernel, Juri Lelli, Ingo Molnar, Clark Williams
On Tue, 2025-10-14 at 11:54 +0200, Peter Zijlstra wrote:
> On Tue, Oct 07, 2025 at 02:29:04PM +0200, Gabriele Monaco wrote:
> > Recent changes to the deadline server leave it running when the system
> > is idle. If the system is idle for longer than the dl_server period and
> > the first scheduling occurs after a fair task wakes up, the algorithm
> > picks the server as the earliest deadline (in the past) and that boosts
> > the fair task that just woke up while:
> > * the deadline is in the past
> > * the server consumed all its runtime (in background)
> > * there is no starvation (idle for about a period)
> >
> > Prevent the server from boosting a task when the deadline is in the
> > past. Instead, replenish a new period and start the server as deferred.
>
> I'm a bit confused, should not enqueue ensure deadline is in the future?
> And if it doesn't shouldn't we fix the enqueue path somewhere?
Enqueue of a deadline task should handle the case, here the CPU is idle and the
deadline server did not stop yet (and won't until the next schedule, if I'm not
mistaken).
The following enqueue of a fair task triggers a schedule where the server (no
longer deferred) boosts the task straight away.
Now the only check for deadline is in pick_next_dl_entity, where the earliest
one is chosen, despite being in the past.
Do you mean to check for deadline when enqueueing the fair task too? I believe
again nothing happens here because the server is still up.
Does it make sense or am I missing something?
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [RFC PATCH] sched/deadline: Avoid dl_server boosting with expired deadline
2025-10-14 10:05 ` Gabriele Monaco
@ 2025-10-14 10:25 ` Peter Zijlstra
2025-10-14 15:32 ` Gabriele Monaco
0 siblings, 1 reply; 26+ messages in thread
From: Peter Zijlstra @ 2025-10-14 10:25 UTC (permalink / raw)
To: Gabriele Monaco; +Cc: linux-kernel, Juri Lelli, Ingo Molnar, Clark Williams
On Tue, Oct 14, 2025 at 12:05:06PM +0200, Gabriele Monaco wrote:
> On Tue, 2025-10-14 at 11:54 +0200, Peter Zijlstra wrote:
> > On Tue, Oct 07, 2025 at 02:29:04PM +0200, Gabriele Monaco wrote:
> > > Recent changes to the deadline server leave it running when the system
> > > is idle. If the system is idle for longer than the dl_server period and
> > > the first scheduling occurs after a fair task wakes up, the algorithm
> > > picks the server as the earliest deadline (in the past) and that boosts
> > > the fair task that just woke up while:
> > > * the deadline is in the past
> > > * the server consumed all its runtime (in background)
> > > * there is no starvation (idle for about a period)
> > >
> > > Prevent the server from boosting a task when the deadline is in the
> > > past. Instead, replenish a new period and start the server as deferred.
> >
> > I'm a bit confused, should not enqueue ensure deadline is in the future?
> > And if it doesn't shouldn't we fix the enqueue path somewhere?
>
> Enqueue of a deadline task should handle the case, here the CPU is idle and the
> deadline server did not stop yet (and won't until the next schedule, if I'm not
> mistaken).
> The following enqueue of a fair task triggers a schedule where the server (no
> longer deferred) boosts the task straight away.
>
> Now the only check for deadline is in pick_next_dl_entity, where the earliest
> one is chosen, despite being in the past.
>
> Do you mean to check for deadline when enqueueing the fair task too? I believe
> again nothing happens here because the server is still up.
>
> Does it make sense or am I missing something?
Lets be confused together :-)
So dl_server is active, but machine is otherwise idle, this means
dl_server_timer is pending, right?
This timer is in one of two states:
- waiting for replenish; which will trigger and switch to 0-laxity.
- waiting for 0-laxity
So that 0-laxity case is the interesting one; when the machine really is
idle, no fair tasks will run and its runtime budget will not get
depleted. Therefore, once we hit 0-laxity, it will do
enqueue_dl_entity(dl_se, ENQUEUE_REPLENISH).
This enqueue should ensure dl_se->deadline is in the future, right?
Anyway, we run this deadline entity (there ain't nothing else to do
anyway), and it finds there aren't any fair tasks, it does
dl_server_stop().
Then, if a fair takes wakes (nr_running: 0->1) and dl_server isn't
active, we do dl_server_start(), which in turn does enqueue_dl_entity().
Now this enqueue is supposed to check if the dl_entity can still run;
does it still have time left in its current period, if not, its
replenish timer time.
So where exactly does the fair task start, and result in dl_se being
on_rq such that dl_deadline is in the past? That sounds like an enqueue
problem to me.
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [RFC PATCH] sched/deadline: Avoid dl_server boosting with expired deadline
2025-10-14 10:25 ` Peter Zijlstra
@ 2025-10-14 15:32 ` Gabriele Monaco
2025-10-14 16:01 ` Juri Lelli
0 siblings, 1 reply; 26+ messages in thread
From: Gabriele Monaco @ 2025-10-14 15:32 UTC (permalink / raw)
To: Peter Zijlstra; +Cc: linux-kernel, Juri Lelli, Ingo Molnar, Clark Williams
On Tue, 2025-10-14 at 12:25 +0200, Peter Zijlstra wrote:
>
> Lets be confused together :-)
>
> So dl_server is active, but machine is otherwise idle, this means
> dl_server_timer is pending, right?
It may not be, as far as I see from the trace, the timer expires at the last
replenish before this "error" and is only restarted a while after, when the
boosted task is throttled by a tick.
>
> This timer is in one of two states:
>
> - waiting for replenish; which will trigger and switch to 0-laxity.
> - waiting for 0-laxity
>
> So that 0-laxity case is the interesting one; when the machine really is
> idle, no fair tasks will run and its runtime budget will not get
> depleted. Therefore, once we hit 0-laxity, it will do
> enqueue_dl_entity(dl_se, ENQUEUE_REPLENISH).
>
> This enqueue should ensure dl_se->deadline is in the future, right?
Yes, this enqueue replenishes (as I can see in the trace), but that doesn't re-
start the timer. The server gets to replenish_dl_entity with dl_defer_armed,
toggles that and doesn't start a timer (should it?).
> Anyway, we run this deadline entity (there ain't nothing else to do
> anyway), and it finds there aren't any fair tasks, it does
> dl_server_stop().
As far as I see, we do reschedule when enqueueing this server entity, but we
don't stop the server (should we though?).
I'm going to gather some more traces to understand what's happening in there.
Here is a trace where you see the schedule just after that replenish before the
error, but no server stop in there (we have tracepoints so you'd see it):
<idle>-0 d.h3. 13.347981: (+3) sched_dl_replenish: comm=server pid=-13 runtime=50000000 deadline=14270340997 yielded=0
<idle>-0 .N.2. 13.348043: (+62) sched_entry: without preemption
<idle>-0 ...2. 13.348048: (+5) sched_exit: without switch
<idle>-0 .N.2. 14.942485: (+1594437) sched_entry: without preemption
<idle>-0 dN.2. 14.942498: (+13) bprint: pick_task_dl: Server picked ksoftirqd/13-126 (runtime 0)
<idle>-0 d..3. 14.942519: (+21) event_nomiss: -13: ready x sched_switch_in -> running
<idle>-0 d..2. 14.942521: (+2) sched_switch: swapper/13:0 [120] R ==> ksoftirqd/13:126 [120]
ksoftirqd/13-126 ...2. 14.942528: (+7) sched_exit: with switch
ksoftirqd/13-126 ...2. 14.942566: (+38) sched_entry: without preemption
ksoftirqd/13-126 d..3. 14.942588: (+22) error_env_nomiss: -13: event dl_throttle not expected in the state running with env clk=593612020
ksoftirqd/13-126 d..3. 14.942592: (+4) sched_dl_throttle: comm=server pid=-13 runtime=-92390 deadline=14270340997 yielded=0
ksoftirqd/13-126 d..3. 14.942601: (+9) sched_dl_replenish: comm=server pid=-13 runtime=50000000 deadline=15864951976 yielded=0
ksoftirqd/13-126 d..2. 14.942623: (+22) sched_switch: ksoftirqd/13:126 [120] S ==> rcuc/13:124 [98]
rcuc/13-124 ...2. 14.942628: (+5) sched_exit: with switch
> Then, if a fair takes wakes (nr_running: 0->1) and dl_server isn't
> active, we do dl_server_start(), which in turn does enqueue_dl_entity().
> Now this enqueue is supposed to check if the dl_entity can still run;
> does it still have time left in its current period, if not, its
> replenish timer time.
>
>
> So where exactly does the fair task start, and result in dl_se being
> on_rq such that dl_deadline is in the past? That sounds like an enqueue
> problem to me.
>
>
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [RFC PATCH] sched/deadline: Avoid dl_server boosting with expired deadline
2025-10-14 15:32 ` Gabriele Monaco
@ 2025-10-14 16:01 ` Juri Lelli
2025-10-14 19:33 ` Peter Zijlstra
0 siblings, 1 reply; 26+ messages in thread
From: Juri Lelli @ 2025-10-14 16:01 UTC (permalink / raw)
To: Gabriele Monaco; +Cc: Peter Zijlstra, linux-kernel, Ingo Molnar, Clark Williams
On 14/10/25 17:32, Gabriele Monaco wrote:
> On Tue, 2025-10-14 at 12:25 +0200, Peter Zijlstra wrote:
> >
> > Lets be confused together :-)
> >
> > So dl_server is active, but machine is otherwise idle, this means
> > dl_server_timer is pending, right?
>
> It may not be, as far as I see from the trace, the timer expires at the last
> replenish before this "error" and is only restarted a while after, when the
> boosted task is throttled by a tick.
>
> >
> > This timer is in one of two states:
> >
> > - waiting for replenish; which will trigger and switch to 0-laxity.
> > - waiting for 0-laxity
> >
> > So that 0-laxity case is the interesting one; when the machine really is
> > idle, no fair tasks will run and its runtime budget will not get
> > depleted. Therefore, once we hit 0-laxity, it will do
> > enqueue_dl_entity(dl_se, ENQUEUE_REPLENISH).
Shouldn't idle time be accounted (subtracted from runtime) as well, though?
> > This enqueue should ensure dl_se->deadline is in the future, right?
>
> Yes, this enqueue replenishes (as I can see in the trace), but that doesn't re-
> start the timer. The server gets to replenish_dl_entity with dl_defer_armed,
> toggles that and doesn't start a timer (should it?).
>
> > Anyway, we run this deadline entity (there ain't nothing else to do
> > anyway), and it finds there aren't any fair tasks, it does
> > dl_server_stop().
>
> As far as I see, we do reschedule when enqueueing this server entity, but we
> don't stop the server (should we though?).
> I'm going to gather some more traces to understand what's happening in there.
>
> Here is a trace where you see the schedule just after that replenish before the
> error, but no server stop in there (we have tracepoints so you'd see it):
>
> <idle>-0 d.h3. 13.347981: (+3) sched_dl_replenish: comm=server pid=-13 runtime=50000000 deadline=14270340997 yielded=0
> <idle>-0 .N.2. 13.348043: (+62) sched_entry: without preemption
> <idle>-0 ...2. 13.348048: (+5) sched_exit: without switch
> <idle>-0 .N.2. 14.942485: (+1594437) sched_entry: without preemption
> <idle>-0 dN.2. 14.942498: (+13) bprint: pick_task_dl: Server picked ksoftirqd/13-126 (runtime 0)
> <idle>-0 d..3. 14.942519: (+21) event_nomiss: -13: ready x sched_switch_in -> running
> <idle>-0 d..2. 14.942521: (+2) sched_switch: swapper/13:0 [120] R ==> ksoftirqd/13:126 [120]
> ksoftirqd/13-126 ...2. 14.942528: (+7) sched_exit: with switch
> ksoftirqd/13-126 ...2. 14.942566: (+38) sched_entry: without preemption
> ksoftirqd/13-126 d..3. 14.942588: (+22) error_env_nomiss: -13: event dl_throttle not expected in the state running with env clk=593612020
> ksoftirqd/13-126 d..3. 14.942592: (+4) sched_dl_throttle: comm=server pid=-13 runtime=-92390 deadline=14270340997 yielded=0
> ksoftirqd/13-126 d..3. 14.942601: (+9) sched_dl_replenish: comm=server pid=-13 runtime=50000000 deadline=15864951976 yielded=0
> ksoftirqd/13-126 d..2. 14.942623: (+22) sched_switch: ksoftirqd/13:126 [120] S ==> rcuc/13:124 [98]
> rcuc/13-124 ...2. 14.942628: (+5) sched_exit: with switch
>
> > Then, if a fair takes wakes (nr_running: 0->1) and dl_server isn't
> > active, we do dl_server_start(), which in turn does enqueue_dl_entity().
> > Now this enqueue is supposed to check if the dl_entity can still run;
> > does it still have time left in its current period, if not, its
> > replenish timer time.
> >
> >
> > So where exactly does the fair task start, and result in dl_se being
> > on_rq such that dl_deadline is in the past? That sounds like an enqueue
> > problem to me.
> >
> >
>
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [RFC PATCH] sched/deadline: Avoid dl_server boosting with expired deadline
2025-10-14 16:01 ` Juri Lelli
@ 2025-10-14 19:33 ` Peter Zijlstra
2025-10-15 5:40 ` Juri Lelli
0 siblings, 1 reply; 26+ messages in thread
From: Peter Zijlstra @ 2025-10-14 19:33 UTC (permalink / raw)
To: Juri Lelli; +Cc: Gabriele Monaco, linux-kernel, Ingo Molnar, Clark Williams
On Tue, Oct 14, 2025 at 06:01:10PM +0200, Juri Lelli wrote:
> Shouldn't idle time be accounted (subtracted from runtime) as well, though?
Argh, indeed. Then I suppose we should look at bringing some of that
'idle-for-whole-period' logic to try and actually stop the timer at some
point if nothing happens.
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [RFC PATCH] sched/deadline: Avoid dl_server boosting with expired deadline
2025-10-14 19:33 ` Peter Zijlstra
@ 2025-10-15 5:40 ` Juri Lelli
2025-10-20 14:11 ` Peter Zijlstra
0 siblings, 1 reply; 26+ messages in thread
From: Juri Lelli @ 2025-10-15 5:40 UTC (permalink / raw)
To: Peter Zijlstra; +Cc: Gabriele Monaco, linux-kernel, Ingo Molnar, Clark Williams
On 14/10/25 21:33, Peter Zijlstra wrote:
> On Tue, Oct 14, 2025 at 06:01:10PM +0200, Juri Lelli wrote:
>
> > Shouldn't idle time be accounted (subtracted from runtime) as well, though?
>
> Argh, indeed. Then I suppose we should look at bringing some of that
> 'idle-for-whole-period' logic to try and actually stop the timer at some
> point if nothing happens.
That was my initial thought. If we get to that replenish after a whole
idle period elapsed, stop the timer (resetting state), so that we can go
back at defer mode with the next enqueue from fair.
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [RFC PATCH] sched/deadline: Avoid dl_server boosting with expired deadline
2025-10-15 5:40 ` Juri Lelli
@ 2025-10-20 14:11 ` Peter Zijlstra
2025-10-22 10:11 ` Gabriele Monaco
2025-11-11 11:37 ` [tip: sched/core] sched/deadline: Fix dl_server time accounting tip-bot2 for Peter Zijlstra
0 siblings, 2 replies; 26+ messages in thread
From: Peter Zijlstra @ 2025-10-20 14:11 UTC (permalink / raw)
To: Juri Lelli
Cc: Gabriele Monaco, linux-kernel, Ingo Molnar, Clark Williams, arighi
On Wed, Oct 15, 2025 at 07:40:18AM +0200, Juri Lelli wrote:
> On 14/10/25 21:33, Peter Zijlstra wrote:
> > On Tue, Oct 14, 2025 at 06:01:10PM +0200, Juri Lelli wrote:
> >
> > > Shouldn't idle time be accounted (subtracted from runtime) as well, though?
> >
> > Argh, indeed. Then I suppose we should look at bringing some of that
> > 'idle-for-whole-period' logic to try and actually stop the timer at some
> > point if nothing happens.
>
> That was my initial thought. If we get to that replenish after a whole
> idle period elapsed, stop the timer (resetting state), so that we can go
> back at defer mode with the next enqueue from fair.
Finally staring at this again; and I'm, as expected, confused again.
So put_prev_task_idle() calls dl_server_update_idle_time(). But this is
only called when we context switch away from idle. The dl_server_timer()
interrupt won't see this, because the interrupt doesn't schedule.
Worse, dl_server_update_idle() only updates p->se.exec_start when it
actually did the update. This means that if !dl_defer, it won't advance
the time, and then when dl_defer it will still see the old timestamp and
include the !dl_defer time.
Also, the enqueue_task_fair() callsite of dl_server_update_idle_time()
is dodgy as heck, the !nr_running check seems to want to ensures p ==
rq->idle, but I'm not sure it actually does.
So how about something like this for starters?
---
diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c
index 7b7671060bf9..963b85dbc477 100644
--- a/kernel/sched/deadline.c
+++ b/kernel/sched/deadline.c
@@ -1166,8 +1166,12 @@ static enum hrtimer_restart dl_server_timer(struct hrtimer *timer, struct sched_
sched_clock_tick();
update_rq_clock(rq);
- if (!dl_se->dl_runtime)
- return HRTIMER_NORESTART;
+ /*
+ * Make sure current has propagated its pending runtime into
+ * any relevant server through calling dl_server_update() and
+ * friends.
+ */
+ rq->curr->sched_class->update_curr(rq);
if (dl_se->dl_defer_armed) {
/*
@@ -1543,35 +1547,16 @@ static void update_curr_dl_se(struct rq *rq, struct sched_dl_entity *dl_se, s64
* as time available for the fair server, avoiding a penalty for the
* rt scheduler that did not consumed that time.
*/
-void dl_server_update_idle_time(struct rq *rq, struct task_struct *p)
+void dl_server_update_idle(struct sched_dl_entity *dl_se, s64 delta_exec)
{
- s64 delta_exec;
-
- if (!rq->fair_server.dl_defer)
- return;
-
- /* no need to discount more */
- if (rq->fair_server.runtime < 0)
- return;
-
- delta_exec = rq_clock_task(rq) - p->se.exec_start;
- if (delta_exec < 0)
- return;
-
- rq->fair_server.runtime -= delta_exec;
-
- if (rq->fair_server.runtime < 0) {
- rq->fair_server.dl_defer_running = 0;
- rq->fair_server.runtime = 0;
- }
-
- p->se.exec_start = rq_clock_task(rq);
+ if (dl_se->dl_server_active && dl_se->dl_runtime && dl_se->dl_defer)
+ update_curr_dl_se(dl_se->rq, dl_se, delta_exec);
}
void dl_server_update(struct sched_dl_entity *dl_se, s64 delta_exec)
{
/* 0 runtime = fair server disabled */
- if (dl_se->dl_runtime)
+ if (dl_se->dl_server_active && dl_se->dl_runtime)
update_curr_dl_se(dl_se->rq, dl_se, delta_exec);
}
@@ -1582,6 +1567,11 @@ void dl_server_start(struct sched_dl_entity *dl_se)
if (!dl_server(dl_se) || dl_se->dl_server_active)
return;
+ /*
+ * Update the current task to 'now'.
+ */
+ rq->curr->sched_class->update_curr(rq);
+
if (WARN_ON_ONCE(!cpu_online(cpu_of(rq))))
return;
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index cee1793e8277..c94c996360e6 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -1239,8 +1239,7 @@ static void update_curr(struct cfs_rq *cfs_rq)
* against fair_server such that it can account for this time
* and possibly avoid running this period.
*/
- if (dl_server_active(&rq->fair_server))
- dl_server_update(&rq->fair_server, delta_exec);
+ dl_server_update(&rq->fair_server, delta_exec);
}
account_cfs_rq_runtime(cfs_rq, delta_exec);
@@ -6996,12 +6995,8 @@ enqueue_task_fair(struct rq *rq, struct task_struct *p, int flags)
h_nr_idle = 1;
}
- if (!rq_h_nr_queued && rq->cfs.h_nr_queued) {
- /* Account for idle runtime */
- if (!rq->nr_running)
- dl_server_update_idle_time(rq, rq->curr);
+ if (!rq_h_nr_queued && rq->cfs.h_nr_queued)
dl_server_start(&rq->fair_server);
- }
/* At this point se is NULL and we are at root level*/
add_nr_running(rq, 1);
diff --git a/kernel/sched/idle.c b/kernel/sched/idle.c
index c39b089d4f09..89cfc26ada46 100644
--- a/kernel/sched/idle.c
+++ b/kernel/sched/idle.c
@@ -452,9 +452,11 @@ static void wakeup_preempt_idle(struct rq *rq, struct task_struct *p, int flags)
resched_curr(rq);
}
+static void update_curr_idle(struct rq *rq);
+
static void put_prev_task_idle(struct rq *rq, struct task_struct *prev, struct task_struct *next)
{
- dl_server_update_idle_time(rq, prev);
+ update_curr_idle(rq);
scx_update_idle(rq, false, true);
}
@@ -511,6 +513,17 @@ prio_changed_idle(struct rq *rq, struct task_struct *p, int oldprio)
static void update_curr_idle(struct rq *rq)
{
+ struct sched_entity *se = &rq->idle->se;
+ u64 now = rq_clock_task(rq);
+ s64 delta_exec;
+
+ delta_exec = now - se->exec_start;
+ if (unlikely(delta_exec <= 0))
+ return;
+
+ se->exec_start = now;
+
+ dl_server_update_idle(&rq->fair_server, delta_exec);
}
/*
diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h
index 1f5d07067f60..3bb1e59c5944 100644
--- a/kernel/sched/sched.h
+++ b/kernel/sched/sched.h
@@ -405,6 +405,7 @@ extern s64 dl_scaled_delta_exec(struct rq *rq, struct sched_dl_entity *dl_se, s6
* naturally thottled to once per period, avoiding high context switch
* workloads from spamming the hrtimer program/cancel paths.
*/
+extern void dl_server_update_idle(struct sched_dl_entity *dl_se, s64 delta_exec);
extern void dl_server_update(struct sched_dl_entity *dl_se, s64 delta_exec);
extern void dl_server_start(struct sched_dl_entity *dl_se);
extern void dl_server_stop(struct sched_dl_entity *dl_se);
@@ -412,8 +413,6 @@ extern void dl_server_init(struct sched_dl_entity *dl_se, struct rq *rq,
dl_server_pick_f pick_task);
extern void sched_init_dl_servers(void);
-extern void dl_server_update_idle_time(struct rq *rq,
- struct task_struct *p);
extern void fair_server_init(struct rq *rq);
extern void __dl_server_attach_root(struct sched_dl_entity *dl_se, struct rq *rq);
extern int dl_server_apply_params(struct sched_dl_entity *dl_se,
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [RFC PATCH] sched/deadline: Avoid dl_server boosting with expired deadline
2025-10-20 14:11 ` Peter Zijlstra
@ 2025-10-22 10:11 ` Gabriele Monaco
2025-10-30 18:42 ` Peter Zijlstra
2025-11-11 11:37 ` [tip: sched/core] sched/deadline: Fix dl_server time accounting tip-bot2 for Peter Zijlstra
1 sibling, 1 reply; 26+ messages in thread
From: Gabriele Monaco @ 2025-10-22 10:11 UTC (permalink / raw)
To: Peter Zijlstra, Juri Lelli
Cc: linux-kernel, Ingo Molnar, Clark Williams, arighi
On Mon, 2025-10-20 at 16:11 +0200, Peter Zijlstra wrote:
> On Wed, Oct 15, 2025 at 07:40:18AM +0200, Juri Lelli wrote:
> > On 14/10/25 21:33, Peter Zijlstra wrote:
> > > On Tue, Oct 14, 2025 at 06:01:10PM +0200, Juri Lelli wrote:
> > >
> > > > Shouldn't idle time be accounted (subtracted from runtime) as well,
> > > > though?
> > >
> > > Argh, indeed. Then I suppose we should look at bringing some of that
> > > 'idle-for-whole-period' logic to try and actually stop the timer at some
> > > point if nothing happens.
> >
> > That was my initial thought. If we get to that replenish after a whole
> > idle period elapsed, stop the timer (resetting state), so that we can go
> > back at defer mode with the next enqueue from fair.
>
> Finally staring at this again; and I'm, as expected, confused again.
>
> So put_prev_task_idle() calls dl_server_update_idle_time(). But this is
> only called when we context switch away from idle. The dl_server_timer()
> interrupt won't see this, because the interrupt doesn't schedule.
>
> Worse, dl_server_update_idle() only updates p->se.exec_start when it
> actually did the update. This means that if !dl_defer, it won't advance
> the time, and then when dl_defer it will still see the old timestamp and
> include the !dl_defer time.
>
> Also, the enqueue_task_fair() callsite of dl_server_update_idle_time()
> is dodgy as heck, the !nr_running check seems to want to ensures p ==
> rq->idle, but I'm not sure it actually does.
>
> So how about something like this for starters?
>
Thanks Peter for sharing this patch, I run it through my test and the model
seems to pass (i.e. no more boosting after deadline). What I found curious
however, is that throughout the test, servers went only through replenish
events.
The system under test is mostly idle (6 periodic dl tasks on a 16 CPUs virtme-ng
VM), so I expect not to see any task boosted by the servers, but in 5 minutes I
didn't even observe any start/stop for the server.
I'm not sure why this is happening, but looking at traces it seems replenish
occurs more often and perhaps doesn't let the server stop:
<idle>-0 [009] d.h3. 14.312395: (+950124) event_nomiss: -9: idle x dl_replenish_idle -> idle
<idle>-0 [009] d.h3. 14.312401: (+6) sched_dl_replenish: comm=server pid=-9 runtime=50000000 deadline=15253307235 yielded=0
<idle>-0 [009] d.h3. 15.262771: (+950370) event_nomiss: -9: idle x dl_replenish_idle -> idle
<idle>-0 [009] d.h3. 15.262781: (+10) sched_dl_replenish: comm=server pid=-9 runtime=50000000 deadline=16203668554 yielded=0
<idle>-0 [009] d.h3. 16.213117: (+950336) event_nomiss: -9: idle x dl_replenish_idle -> idle
<idle>-0 [009] d.h3. 16.213123: (+6) sched_dl_replenish: comm=server pid=-9 runtime=50000000 deadline=17154029879 yielded=0
Is this expected?
Thanks,
Gabriele
> ---
> diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c
> index 7b7671060bf9..963b85dbc477 100644
> --- a/kernel/sched/deadline.c
> +++ b/kernel/sched/deadline.c
> @@ -1166,8 +1166,12 @@ static enum hrtimer_restart dl_server_timer(struct
> hrtimer *timer, struct sched_
> sched_clock_tick();
> update_rq_clock(rq);
>
> - if (!dl_se->dl_runtime)
> - return HRTIMER_NORESTART;
> + /*
> + * Make sure current has propagated its pending runtime into
> + * any relevant server through calling dl_server_update() and
> + * friends.
> + */
> + rq->curr->sched_class->update_curr(rq);
>
> if (dl_se->dl_defer_armed) {
> /*
> @@ -1543,35 +1547,16 @@ static void update_curr_dl_se(struct rq *rq, struct
> sched_dl_entity *dl_se, s64
> * as time available for the fair server, avoiding a penalty for the
> * rt scheduler that did not consumed that time.
> */
> -void dl_server_update_idle_time(struct rq *rq, struct task_struct *p)
> +void dl_server_update_idle(struct sched_dl_entity *dl_se, s64 delta_exec)
> {
> - s64 delta_exec;
> -
> - if (!rq->fair_server.dl_defer)
> - return;
> -
> - /* no need to discount more */
> - if (rq->fair_server.runtime < 0)
> - return;
> -
> - delta_exec = rq_clock_task(rq) - p->se.exec_start;
> - if (delta_exec < 0)
> - return;
> -
> - rq->fair_server.runtime -= delta_exec;
> -
> - if (rq->fair_server.runtime < 0) {
> - rq->fair_server.dl_defer_running = 0;
> - rq->fair_server.runtime = 0;
> - }
> -
> - p->se.exec_start = rq_clock_task(rq);
> + if (dl_se->dl_server_active && dl_se->dl_runtime && dl_se->dl_defer)
> + update_curr_dl_se(dl_se->rq, dl_se, delta_exec);
> }
>
> void dl_server_update(struct sched_dl_entity *dl_se, s64 delta_exec)
> {
> /* 0 runtime = fair server disabled */
> - if (dl_se->dl_runtime)
> + if (dl_se->dl_server_active && dl_se->dl_runtime)
> update_curr_dl_se(dl_se->rq, dl_se, delta_exec);
> }
>
> @@ -1582,6 +1567,11 @@ void dl_server_start(struct sched_dl_entity *dl_se)
> if (!dl_server(dl_se) || dl_se->dl_server_active)
> return;
>
> + /*
> + * Update the current task to 'now'.
> + */
> + rq->curr->sched_class->update_curr(rq);
> +
> if (WARN_ON_ONCE(!cpu_online(cpu_of(rq))))
> return;
>
> diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
> index cee1793e8277..c94c996360e6 100644
> --- a/kernel/sched/fair.c
> +++ b/kernel/sched/fair.c
> @@ -1239,8 +1239,7 @@ static void update_curr(struct cfs_rq *cfs_rq)
> * against fair_server such that it can account for this
> time
> * and possibly avoid running this period.
> */
> - if (dl_server_active(&rq->fair_server))
> - dl_server_update(&rq->fair_server, delta_exec);
> + dl_server_update(&rq->fair_server, delta_exec);
> }
>
> account_cfs_rq_runtime(cfs_rq, delta_exec);
> @@ -6996,12 +6995,8 @@ enqueue_task_fair(struct rq *rq, struct task_struct *p,
> int flags)
> h_nr_idle = 1;
> }
>
> - if (!rq_h_nr_queued && rq->cfs.h_nr_queued) {
> - /* Account for idle runtime */
> - if (!rq->nr_running)
> - dl_server_update_idle_time(rq, rq->curr);
> + if (!rq_h_nr_queued && rq->cfs.h_nr_queued)
> dl_server_start(&rq->fair_server);
> - }
>
> /* At this point se is NULL and we are at root level*/
> add_nr_running(rq, 1);
> diff --git a/kernel/sched/idle.c b/kernel/sched/idle.c
> index c39b089d4f09..89cfc26ada46 100644
> --- a/kernel/sched/idle.c
> +++ b/kernel/sched/idle.c
> @@ -452,9 +452,11 @@ static void wakeup_preempt_idle(struct rq *rq, struct
> task_struct *p, int flags)
> resched_curr(rq);
> }
>
> +static void update_curr_idle(struct rq *rq);
> +
> static void put_prev_task_idle(struct rq *rq, struct task_struct *prev,
> struct task_struct *next)
> {
> - dl_server_update_idle_time(rq, prev);
> + update_curr_idle(rq);
> scx_update_idle(rq, false, true);
> }
>
> @@ -511,6 +513,17 @@ prio_changed_idle(struct rq *rq, struct task_struct *p,
> int oldprio)
>
> static void update_curr_idle(struct rq *rq)
> {
> + struct sched_entity *se = &rq->idle->se;
> + u64 now = rq_clock_task(rq);
> + s64 delta_exec;
> +
> + delta_exec = now - se->exec_start;
> + if (unlikely(delta_exec <= 0))
> + return;
> +
> + se->exec_start = now;
> +
> + dl_server_update_idle(&rq->fair_server, delta_exec);
> }
>
> /*
> diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h
> index 1f5d07067f60..3bb1e59c5944 100644
> --- a/kernel/sched/sched.h
> +++ b/kernel/sched/sched.h
> @@ -405,6 +405,7 @@ extern s64 dl_scaled_delta_exec(struct rq *rq, struct
> sched_dl_entity *dl_se, s6
> * naturally thottled to once per period, avoiding high context switch
> * workloads from spamming the hrtimer program/cancel paths.
> */
> +extern void dl_server_update_idle(struct sched_dl_entity *dl_se, s64
> delta_exec);
> extern void dl_server_update(struct sched_dl_entity *dl_se, s64 delta_exec);
> extern void dl_server_start(struct sched_dl_entity *dl_se);
> extern void dl_server_stop(struct sched_dl_entity *dl_se);
> @@ -412,8 +413,6 @@ extern void dl_server_init(struct sched_dl_entity *dl_se,
> struct rq *rq,
> dl_server_pick_f pick_task);
> extern void sched_init_dl_servers(void);
>
> -extern void dl_server_update_idle_time(struct rq *rq,
> - struct task_struct *p);
> extern void fair_server_init(struct rq *rq);
> extern void __dl_server_attach_root(struct sched_dl_entity *dl_se, struct rq
> *rq);
> extern int dl_server_apply_params(struct sched_dl_entity *dl_se,
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [RFC PATCH] sched/deadline: Avoid dl_server boosting with expired deadline
2025-10-22 10:11 ` Gabriele Monaco
@ 2025-10-30 18:42 ` Peter Zijlstra
2025-10-31 13:05 ` Peter Zijlstra
0 siblings, 1 reply; 26+ messages in thread
From: Peter Zijlstra @ 2025-10-30 18:42 UTC (permalink / raw)
To: Gabriele Monaco
Cc: Juri Lelli, linux-kernel, Ingo Molnar, Clark Williams, arighi
On Wed, Oct 22, 2025 at 12:11:51PM +0200, Gabriele Monaco wrote:
Sorry, finally cycling back to this.
> > So how about something like this for starters?
> >
>
> Thanks Peter for sharing this patch, I run it through my test and the model
> seems to pass (i.e. no more boosting after deadline). What I found curious
> however, is that throughout the test, servers went only through replenish
> events.
> The system under test is mostly idle (6 periodic dl tasks on a 16 CPUs virtme-ng
> VM), so I expect not to see any task boosted by the servers, but in 5 minutes I
> didn't even observe any start/stop for the server.
>
> I'm not sure why this is happening, but looking at traces it seems replenish
> occurs more often and perhaps doesn't let the server stop:
>
> <idle>-0 [009] d.h3. 14.312395: (+950124) event_nomiss: -9: idle x dl_replenish_idle -> idle
> <idle>-0 [009] d.h3. 14.312401: (+6) sched_dl_replenish: comm=server pid=-9 runtime=50000000 deadline=15253307235 yielded=0
> <idle>-0 [009] d.h3. 15.262771: (+950370) event_nomiss: -9: idle x dl_replenish_idle -> idle
> <idle>-0 [009] d.h3. 15.262781: (+10) sched_dl_replenish: comm=server pid=-9 runtime=50000000 deadline=16203668554 yielded=0
> <idle>-0 [009] d.h3. 16.213117: (+950336) event_nomiss: -9: idle x dl_replenish_idle -> idle
> <idle>-0 [009] d.h3. 16.213123: (+6) sched_dl_replenish: comm=server pid=-9 runtime=50000000 deadline=17154029879 yielded=0
>
> Is this expected?
Sort of, that was next on the list. Let me see if I can make it stop a
little more.
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [RFC PATCH] sched/deadline: Avoid dl_server boosting with expired deadline
2025-10-30 18:42 ` Peter Zijlstra
@ 2025-10-31 13:05 ` Peter Zijlstra
2025-10-31 13:24 ` Gabriele Monaco
0 siblings, 1 reply; 26+ messages in thread
From: Peter Zijlstra @ 2025-10-31 13:05 UTC (permalink / raw)
To: Gabriele Monaco
Cc: Juri Lelli, linux-kernel, Ingo Molnar, Clark Williams, arighi
On Thu, Oct 30, 2025 at 07:42:05PM +0100, Peter Zijlstra wrote:
> On Wed, Oct 22, 2025 at 12:11:51PM +0200, Gabriele Monaco wrote:
>
> Sorry, finally cycling back to this.
>
> > > So how about something like this for starters?
> > >
> >
> > Thanks Peter for sharing this patch, I run it through my test and the model
> > seems to pass (i.e. no more boosting after deadline). What I found curious
> > however, is that throughout the test, servers went only through replenish
> > events.
> > The system under test is mostly idle (6 periodic dl tasks on a 16 CPUs virtme-ng
> > VM), so I expect not to see any task boosted by the servers, but in 5 minutes I
> > didn't even observe any start/stop for the server.
> >
> > I'm not sure why this is happening, but looking at traces it seems replenish
> > occurs more often and perhaps doesn't let the server stop:
> >
> > <idle>-0 [009] d.h3. 14.312395: (+950124) event_nomiss: -9: idle x dl_replenish_idle -> idle
> > <idle>-0 [009] d.h3. 14.312401: (+6) sched_dl_replenish: comm=server pid=-9 runtime=50000000 deadline=15253307235 yielded=0
> > <idle>-0 [009] d.h3. 15.262771: (+950370) event_nomiss: -9: idle x dl_replenish_idle -> idle
> > <idle>-0 [009] d.h3. 15.262781: (+10) sched_dl_replenish: comm=server pid=-9 runtime=50000000 deadline=16203668554 yielded=0
> > <idle>-0 [009] d.h3. 16.213117: (+950336) event_nomiss: -9: idle x dl_replenish_idle -> idle
> > <idle>-0 [009] d.h3. 16.213123: (+6) sched_dl_replenish: comm=server pid=-9 runtime=50000000 deadline=17154029879 yielded=0
> >
> > Is this expected?
>
> Sort of, that was next on the list. Let me see if I can make it stop a
> little more.
OK, so I've gone over things again and all I got was a comment.
That is, today I think it all works as expected.
The dl_server will stop once the fair class goes idle long enough. Can
you confirm this?
---
--- a/kernel/sched/deadline.c
+++ b/kernel/sched/deadline.c
@@ -1152,6 +1152,94 @@ static void __push_dl_task(struct rq *rq
/* a defer timer will not be reset if the runtime consumed was < dl_server_min_res */
static const u64 dl_server_min_res = 1 * NSEC_PER_MSEC;
+
+/*
+ * dl_server && dl_defer:
+ * dl_defer_armed = 0
+ * dl_defer_running = 0
+ * dl_throttled = 0
+ *
+ * [1] dl_server_start()
+ * dl_server_active = 1;
+ * enqueue_dl_entity()
+ * update_dl_entity(WAKEUP)
+ * if (!dl_defer_running)
+ * dl_defer_armed = 1;
+ * dl_defer_throttled = 1;
+ * if (dl_throttled && start_dl_timer())
+ * return;
+ * // start server into waiting for zero-laxity
+ *
+ * // deplete server runtime from fair-class
+ * [2] update_curr_dl_se()
+ * if (dl_defer && dl_throttled && dl_runtime_exceeded())
+ * dl_defer_running = 0;
+ * hrtimer_try_to_cancel(); // stop timer
+ * replenish_dl_new_period()
+ * // advance period
+ * dl_throttled = 1;
+ * dl_defer_armed = 1;
+ * start_dl_timer(); // restart timer
+ * // back into waiting for zero-laxity
+ *
+ * // timer actually fires means we have runtime
+ * [4] dl_server_timer()
+ * if (dl_defer_armed)
+ * dl_defer_running = 1;
+ * enqueue_dl_entity(REPLENISH)
+ * replenish_dl_entity()
+ * opt-fwd-period
+ * if (dl_throttled)
+ * dl_throttled = 0;
+ * if (dl_defer_armed)
+ * dl_defer_armed = 0;
+ * __enqueue_dl_entity();
+ * // server queued
+ *
+ * // schedule server
+ * [5] pick_task_dl()
+ * p = server_pick_task();
+ * if (!p)
+ * dl_server_stop()
+ * dequeue_dl_entity();
+ * hrtimer_try_to_cancel();
+ * dl_defer_armed = 0;
+ * dl_throttled = 0;
+ * dl_server_active = 0;
+ * // goto [1]
+ *
+ * // server running
+ * [6] update_curr_dl_se()
+ * if (dl_runtime_exceeded())
+ * dl_throttled = 1;
+ * dequeue_dl_entity();
+ * start_dl_timer();
+ * // replenish-timer
+ *
+ * // goto [2]
+ *
+ * [7] dl_server_timer()
+ * enqueue_dl_entity(REPLENISH)
+ * replenish_dl_entity()
+ * fwd-period
+ * if (dl_throttled)
+ * dl_throttled = 0;
+ * __enqueue_dl_entity();
+ * // goto [5]
+ *
+ * Notes:
+ *
+ * - When there are fair tasks running the most likely loop is [2]->[2].
+ * the dl_server never actually runs, the timer never fires.
+ *
+ * - When there is actual fair starvation; the timer fires and starts the
+ * dl_server. This will then throttle and replenish like a normal DL
+ * task. Notably it will not 'defer' again.
+ *
+ * - When fair goes idle, it will not consume dl_server budget so the server
+ * will start. However, it will find there are no fair tasks to run and
+ * stop itself.
+ */
static enum hrtimer_restart dl_server_timer(struct hrtimer *timer, struct sched_dl_entity *dl_se)
{
struct rq *rq = rq_of_dl_se(dl_se);
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [RFC PATCH] sched/deadline: Avoid dl_server boosting with expired deadline
2025-10-31 13:05 ` Peter Zijlstra
@ 2025-10-31 13:24 ` Gabriele Monaco
2025-10-31 15:20 ` Peter Zijlstra
0 siblings, 1 reply; 26+ messages in thread
From: Gabriele Monaco @ 2025-10-31 13:24 UTC (permalink / raw)
To: Peter Zijlstra
Cc: Juri Lelli, linux-kernel, Ingo Molnar, Clark Williams, arighi
On Fri, 2025-10-31 at 14:05 +0100, Peter Zijlstra wrote:
> On Thu, Oct 30, 2025 at 07:42:05PM +0100, Peter Zijlstra wrote:
> > On Wed, Oct 22, 2025 at 12:11:51PM +0200, Gabriele Monaco wrote:
> > >
> > > Is this expected?
> >
> > Sort of, that was next on the list. Let me see if I can make it stop a
> > little more.
>
> OK, so I've gone over things again and all I got was a comment.
>
> That is, today I think it all works as expected.
>
> The dl_server will stop once the fair class goes idle long enough. Can
> you confirm this?
>
I'm going to go through your comment more carefully, but what I can observe now
is a bit different:
After this patch, consuming bandwidth in background on fair tasks and on idle is
equivalent. Updating idle time does effectively replenish after exhausting
runtime and we never stop the server (IMO this is correct behaviour only for
fair tasks, since there's potentially something to do).
At least this is the behaviour I get on a mostly idle system.
Different scenario if I have the CPU busy with other tasks (e.g. RT policies),
there I can see the server stopping and starting again.
After I do this I seem to get a different behaviour (even some boosting after
idle), I'm trying to understand what's going on.
Does this behaviour make sense to you?
Thanks,
Gabriele
> ---
> --- a/kernel/sched/deadline.c
> +++ b/kernel/sched/deadline.c
> @@ -1152,6 +1152,94 @@ static void __push_dl_task(struct rq *rq
> /* a defer timer will not be reset if the runtime consumed was <
> dl_server_min_res */
> static const u64 dl_server_min_res = 1 * NSEC_PER_MSEC;
>
> +
> +/*
> + * dl_server && dl_defer:
> + * dl_defer_armed = 0
> + * dl_defer_running = 0
> + * dl_throttled = 0
> + *
> + * [1] dl_server_start()
> + * dl_server_active = 1;
> + * enqueue_dl_entity()
> + * update_dl_entity(WAKEUP)
> + * if (!dl_defer_running)
> + * dl_defer_armed = 1;
> + * dl_defer_throttled = 1;
> + * if (dl_throttled && start_dl_timer())
> + * return;
> + * // start server into waiting for zero-laxity
> + *
> + * // deplete server runtime from fair-class
> + * [2] update_curr_dl_se()
> + * if (dl_defer && dl_throttled && dl_runtime_exceeded())
> + * dl_defer_running = 0;
> + * hrtimer_try_to_cancel(); // stop timer
> + * replenish_dl_new_period()
> + * // advance period
> + * dl_throttled = 1;
> + * dl_defer_armed = 1;
> + * start_dl_timer(); // restart timer
> + * // back into waiting for zero-laxity
> + *
> + * // timer actually fires means we have runtime
> + * [4] dl_server_timer()
> + * if (dl_defer_armed)
> + * dl_defer_running = 1;
> + * enqueue_dl_entity(REPLENISH)
> + * replenish_dl_entity()
> + * opt-fwd-period
> + * if (dl_throttled)
> + * dl_throttled = 0;
> + * if (dl_defer_armed)
> + * dl_defer_armed = 0;
> + * __enqueue_dl_entity();
> + * // server queued
> + *
> + * // schedule server
> + * [5] pick_task_dl()
> + * p = server_pick_task();
> + * if (!p)
> + * dl_server_stop()
> + * dequeue_dl_entity();
> + * hrtimer_try_to_cancel();
> + * dl_defer_armed = 0;
> + * dl_throttled = 0;
> + * dl_server_active = 0;
> + * // goto [1]
> + *
> + * // server running
> + * [6] update_curr_dl_se()
> + * if (dl_runtime_exceeded())
> + * dl_throttled = 1;
> + * dequeue_dl_entity();
> + * start_dl_timer();
> + * // replenish-timer
> + *
> + * // goto [2]
> + *
> + * [7] dl_server_timer()
> + * enqueue_dl_entity(REPLENISH)
> + * replenish_dl_entity()
> + * fwd-period
> + * if (dl_throttled)
> + * dl_throttled = 0;
> + * __enqueue_dl_entity();
> + * // goto [5]
> + *
> + * Notes:
> + *
> + * - When there are fair tasks running the most likely loop is [2]->[2].
> + * the dl_server never actually runs, the timer never fires.
> + *
> + * - When there is actual fair starvation; the timer fires and starts the
> + * dl_server. This will then throttle and replenish like a normal DL
> + * task. Notably it will not 'defer' again.
> + *
> + * - When fair goes idle, it will not consume dl_server budget so the server
> + * will start. However, it will find there are no fair tasks to run and
> + * stop itself.
> + */
> static enum hrtimer_restart dl_server_timer(struct hrtimer *timer, struct
> sched_dl_entity *dl_se)
> {
> struct rq *rq = rq_of_dl_se(dl_se);
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [RFC PATCH] sched/deadline: Avoid dl_server boosting with expired deadline
2025-10-31 13:24 ` Gabriele Monaco
@ 2025-10-31 15:20 ` Peter Zijlstra
2025-10-31 15:41 ` Gabriele Monaco
0 siblings, 1 reply; 26+ messages in thread
From: Peter Zijlstra @ 2025-10-31 15:20 UTC (permalink / raw)
To: Gabriele Monaco
Cc: Juri Lelli, linux-kernel, Ingo Molnar, Clark Williams, arighi
On Fri, Oct 31, 2025 at 02:24:17PM +0100, Gabriele Monaco wrote:
> On Fri, 2025-10-31 at 14:05 +0100, Peter Zijlstra wrote:
> > On Thu, Oct 30, 2025 at 07:42:05PM +0100, Peter Zijlstra wrote:
> > > On Wed, Oct 22, 2025 at 12:11:51PM +0200, Gabriele Monaco wrote:
> > > >
> > > > Is this expected?
> > >
> > > Sort of, that was next on the list. Let me see if I can make it stop a
> > > little more.
> >
> > OK, so I've gone over things again and all I got was a comment.
> >
> > That is, today I think it all works as expected.
> >
> > The dl_server will stop once the fair class goes idle long enough. Can
> > you confirm this?
> >
>
> I'm going to go through your comment more carefully, but what I can observe now
> is a bit different:
>
> After this patch, consuming bandwidth in background on fair tasks and on idle is
> equivalent. Updating idle time does effectively replenish after exhausting
> runtime and we never stop the server (IMO this is correct behaviour only for
> fair tasks, since there's potentially something to do).
> At least this is the behaviour I get on a mostly idle system.
>
> Different scenario if I have the CPU busy with other tasks (e.g. RT policies),
> there I can see the server stopping and starting again.
> After I do this I seem to get a different behaviour (even some boosting after
> idle), I'm trying to understand what's going on.
>
> Does this behaviour make sense to you?
Ooooh, because idle time is accounted against the server budget too.
That is, idle and running fair are both [2] in my comment. So we never
get to [5].
Humm, let me noodle a bit more on this. Also, I should see if I can get
graphviz to draw ascii art state diagrams :-)
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [RFC PATCH] sched/deadline: Avoid dl_server boosting with expired deadline
2025-10-31 15:20 ` Peter Zijlstra
@ 2025-10-31 15:41 ` Gabriele Monaco
2025-10-31 15:44 ` Peter Zijlstra
2025-11-01 0:08 ` [RFC PATCH] sched/deadline: Avoid dl_server boosting with expired deadline Peter Zijlstra
0 siblings, 2 replies; 26+ messages in thread
From: Gabriele Monaco @ 2025-10-31 15:41 UTC (permalink / raw)
To: Peter Zijlstra
Cc: Juri Lelli, linux-kernel, Ingo Molnar, Clark Williams, arighi
On Fri, 2025-10-31 at 16:20 +0100, Peter Zijlstra wrote:
> On Fri, Oct 31, 2025 at 02:24:17PM +0100, Gabriele Monaco wrote:
> >
> > Different scenario if I have the CPU busy with other tasks (e.g. RT
> > policies), there I can see the server stopping and starting again.
> > After I do this I seem to get a different behaviour (even some boosting
> > after idle), I'm trying to understand what's going on.
> >
After running some heavy RT workload (stress-ng --cpu 10 --sched rr) I do see
the server stopping and starting as the models would expect, but somehow it's
always boosting as soon as it's started.
Apparently dl_defer_running is always 1 in that scenario. Perhaps running idle
counts as running something too, so it never defers. But I can't really see how
this happens..
<idle>-0 12.964637: (+8) sched_switch: swapper/9:0 [120] R ==> irq/50:212 [49]
irq/50-212 12.964699: (+58) sched_wakeup: trace-cmd:544 [120] CPU:000
irq/50-212 12.964831: (+24) sched_migrate_task: comm=trace-cmd pid=544 prio=120 orig_cpu=0 dest_cpu=9
irq/50-212 12.964842: (+11) event_nomiss: -9: idle x dl_server_start -> idle
irq/50-212 12.964843: (+1) sched_dl_server_start: comm=server pid=-9 runtime=16238881 deadline=13592407587 yielded=0
irq/50-212 12.964854: (+11) bprint: pick_task_dl: Picking server 9 (armed 1 running 1)
irq/50-212 12.964861: (+7) event_nomiss: -9: idle x sched_switch_in -> running
irq/50-212 12.964862: (+1) sched_switch: irq/50:212 [49] S ==> trace-cmd:544 [120]
The trace-cmd in the example above is boosted, like highlighted by that bprint
just before.
> Ooooh, because idle time is accounted against the server budget too.
>
> That is, idle and running fair are both [2] in my comment. So we never
> get to [5].
Yeah that must be it, however I don't think it explains the other curious
behaviour above.
> Humm, let me noodle a bit more on this. Also, I should see if I can get
> graphviz to draw ascii art state diagrams :-)
Dot doesn't do it but graph-easy does reasonably well.
Thanks,
Gabriele
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [RFC PATCH] sched/deadline: Avoid dl_server boosting with expired deadline
2025-10-31 15:41 ` Gabriele Monaco
@ 2025-10-31 15:44 ` Peter Zijlstra
2025-10-31 15:51 ` Gabriele Monaco
2025-11-01 0:00 ` Peter Zijlstra
2025-11-01 0:08 ` [RFC PATCH] sched/deadline: Avoid dl_server boosting with expired deadline Peter Zijlstra
1 sibling, 2 replies; 26+ messages in thread
From: Peter Zijlstra @ 2025-10-31 15:44 UTC (permalink / raw)
To: Gabriele Monaco
Cc: Juri Lelli, linux-kernel, Ingo Molnar, Clark Williams, arighi
On Fri, Oct 31, 2025 at 04:41:22PM +0100, Gabriele Monaco wrote:
> > Humm, let me noodle a bit more on this. Also, I should see if I can get
> > graphviz to draw ascii art state diagrams :-)
>
> Dot doesn't do it but graph-easy does reasonably well.
yep, just found that. Seems to work well:
+----------------------+
| A:init | <+
+----------------------+ |
| |
| 1:dl_server_start | 4:pick_task_dl
v |
2:update_curr_dl_se +----------------------+ |
+---------------------- | | |
| | B:zero_laxity-wait | |
+---------------------> | | <+------------------+
+----------------------+ | |
| | |
| 3:dl_server_timer | |
v | |
+----------------------+ | |
+---------------------> | C:running | -+ | 2:update_curr_dl_se
| +----------------------+ |
| | |
| 6:dl_server_timer | 5:update_curr_dl_se |
| v |
| +----------------------+ |
+---------------------- | D:replenish | --------------------+
+----------------------+
Anyway, back to noodling on how to make it stop on idle :-)
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [RFC PATCH] sched/deadline: Avoid dl_server boosting with expired deadline
2025-10-31 15:44 ` Peter Zijlstra
@ 2025-10-31 15:51 ` Gabriele Monaco
2025-11-01 0:00 ` Peter Zijlstra
1 sibling, 0 replies; 26+ messages in thread
From: Gabriele Monaco @ 2025-10-31 15:51 UTC (permalink / raw)
To: Peter Zijlstra
Cc: Juri Lelli, linux-kernel, Ingo Molnar, Clark Williams, arighi
On Fri, 2025-10-31 at 16:44 +0100, Peter Zijlstra wrote:
> On Fri, Oct 31, 2025 at 04:41:22PM +0100, Gabriele Monaco wrote:
>
> > > Humm, let me noodle a bit more on this. Also, I should see if I can get
> > > graphviz to draw ascii art state diagrams :-)
> >
> > Dot doesn't do it but graph-easy does reasonably well.
>
> yep, just found that. Seems to work well:
>
Nice! This can become also a model by the way. But I'd need a few more
tracepoints ;)
>
> +----------------------+
> | A:init | <+
> +----------------------+ |
> | |
> | 1:dl_server_start | 4:pick_task_dl
> v |
> 2:update_curr_dl_se +----------------------+ |
> +---------------------- | | |
> | | B:zero_laxity-wait | |
> +---------------------> | | <+------------------+
> +----------------------+ | |
> | | |
> | 3:dl_server_timer | |
> v | |
> +----------------------+ | |
> +---------------------> | C:running | -+ |
> 2:update_curr_dl_se
> | +----------------------+ |
> | | |
> | 6:dl_server_timer | 5:update_curr_dl_se |
> | v |
> | +----------------------+ |
> +---------------------- | D:replenish | --------------------+
> +----------------------+
>
> Anyway, back to noodling on how to make it stop on idle :-)
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [RFC PATCH] sched/deadline: Avoid dl_server boosting with expired deadline
2025-10-31 15:44 ` Peter Zijlstra
2025-10-31 15:51 ` Gabriele Monaco
@ 2025-11-01 0:00 ` Peter Zijlstra
2025-11-11 9:58 ` Gabriele Monaco
2025-11-11 11:37 ` [tip: sched/core] sched/deadline: Fix dl_server stop condition tip-bot2 for Peter Zijlstra
1 sibling, 2 replies; 26+ messages in thread
From: Peter Zijlstra @ 2025-11-01 0:00 UTC (permalink / raw)
To: Gabriele Monaco
Cc: Juri Lelli, linux-kernel, Ingo Molnar, Clark Williams, arighi
On Fri, Oct 31, 2025 at 04:44:55PM +0100, Peter Zijlstra wrote:
> Anyway, back to noodling on how to make it stop on idle :-)
This seems to behave (at least, it did before the cleanup).
It has the fancy comment, ascii-art and everything. Hopefully we'll all
get less confused when looking at this in the future.
---
include/linux/sched.h | 15 +--
kernel/sched/deadline.c | 233 +++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 240 insertions(+), 8 deletions(-)
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -685,20 +685,22 @@ struct sched_dl_entity {
*
* @dl_server tells if this is a server entity.
*
- * @dl_defer tells if this is a deferred or regular server. For
- * now only defer server exists.
- *
- * @dl_defer_armed tells if the deferrable server is waiting
- * for the replenishment timer to activate it.
- *
* @dl_server_active tells if the dlserver is active(started).
* dlserver is started on first cfs enqueue on an idle runqueue
* and is stopped when a dequeue results in 0 cfs tasks on the
* runqueue. In other words, dlserver is active only when cpu's
* runqueue has atleast one cfs task.
*
+ * @dl_defer tells if this is a deferred or regular server. For
+ * now only defer server exists.
+ *
+ * @dl_defer_armed tells if the deferrable server is waiting
+ * for the replenishment timer to activate it.
+ *
* @dl_defer_running tells if the deferrable server is actually
* running, skipping the defer phase.
+ *
+ * @dl_defer_idle tracks idle state
*/
unsigned int dl_throttled : 1;
unsigned int dl_yielded : 1;
@@ -709,6 +711,7 @@ struct sched_dl_entity {
unsigned int dl_defer : 1;
unsigned int dl_defer_armed : 1;
unsigned int dl_defer_running : 1;
+ unsigned int dl_defer_idle : 1;
/*
* Bandwidth enforcement timer. Each -deadline task has its
--- a/kernel/sched/deadline.c
+++ b/kernel/sched/deadline.c
@@ -1173,6 +1173,11 @@ static enum hrtimer_restart dl_server_ti
*/
rq->curr->sched_class->update_curr(rq);
+ if (dl_se->dl_defer_idle) {
+ dl_server_stop(dl_se);
+ return HRTIMER_NORESTART;
+ }
+
if (dl_se->dl_defer_armed) {
/*
* First check if the server could consume runtime in background.
@@ -1420,10 +1425,11 @@ s64 dl_scaled_delta_exec(struct rq *rq,
}
static inline void
-update_stats_dequeue_dl(struct dl_rq *dl_rq, struct sched_dl_entity *dl_se,
- int flags);
+update_stats_dequeue_dl(struct dl_rq *dl_rq, struct sched_dl_entity *dl_se, int flags);
+
static void update_curr_dl_se(struct rq *rq, struct sched_dl_entity *dl_se, s64 delta_exec)
{
+ bool idle = rq->curr == rq->idle;
s64 scaled_delta_exec;
if (unlikely(delta_exec <= 0)) {
@@ -1444,6 +1450,9 @@ static void update_curr_dl_se(struct rq
dl_se->runtime -= scaled_delta_exec;
+ if (dl_se->dl_defer_idle && !idle)
+ dl_se->dl_defer_idle = 0;
+
/*
* The fair server can consume its runtime while throttled (not queued/
* running as regular CFS).
@@ -1454,6 +1463,29 @@ static void update_curr_dl_se(struct rq
*/
if (dl_se->dl_defer && dl_se->dl_throttled && dl_runtime_exceeded(dl_se)) {
/*
+ * Non-servers would never get time accounted while throttled.
+ */
+ WARN_ON_ONCE(!dl_server(dl_se));
+
+ /*
+ * While the server is marked idle, do not push out the
+ * activation further, instead wait for the period timer
+ * to lapse and stop the server.
+ */
+ if (dl_se->dl_defer_idle && idle) {
+ /*
+ * The timer is at the zero-laxity point, this means
+ * dl_server_stop() / dl_server_start() can happen
+ * while now < deadline. This means update_dl_entity()
+ * will not replenish. Additionally start_dl_timer()
+ * will be set for 'deadline - runtime'. Negative
+ * runtime will not do.
+ */
+ dl_se->runtime = 0;
+ return;
+ }
+
+ /*
* If the server was previously activated - the starving condition
* took place, it this point it went away because the fair scheduler
* was able to get runtime in background. So return to the initial
@@ -1465,6 +1497,9 @@ static void update_curr_dl_se(struct rq
replenish_dl_new_period(dl_se, dl_se->rq);
+ if (idle)
+ dl_se->dl_defer_idle = 1;
+
/*
* Not being able to start the timer seems problematic. If it could not
* be started for whatever reason, we need to "unthrottle" the DL server
@@ -1560,6 +1595,199 @@ void dl_server_update(struct sched_dl_en
update_curr_dl_se(dl_se->rq, dl_se, delta_exec);
}
+/*
+ * dl_server && dl_defer:
+ *
+ * 6
+ * +--------------------+
+ * v |
+ * +-------------+ 4 +-----------+ 5 +------------------+
+ * +-> | A:init | <--- | D:running | -----> | E:replenish-wait |
+ * | +-------------+ +-----------+ +------------------+
+ * | | | 1 ^ ^ |
+ * | | 1 +----------+ | 3 |
+ * | v | |
+ * | +--------------------------------+ 2 |
+ * | | | ----+ |
+ * | 8 | B:zero_laxity-wait | | |
+ * | | | <---+ |
+ * | +--------------------------------+ |
+ * | | ^ ^ 2 |
+ * | | 7 | 2 +--------------------+
+ * | v |
+ * | +-------------+ |
+ * +-- | C:idle-wait | -+
+ * +-------------+
+ * ^ 7 |
+ * +---------+
+ *
+ *
+ * [A] - init
+ * dl_server_active = 0
+ * dl_throttled = 0
+ * dl_defer_armed = 0
+ * dl_defer_running = 0/1
+ * dl_defer_idle = 0
+ *
+ * [B] - zero_laxity-wait
+ * dl_server_active = 1
+ * dl_throttled = 1
+ * dl_defer_armed = 1
+ * dl_defer_running = 0
+ * dl_defer_idle = 0
+ *
+ * [C] - idle-wait
+ * dl_server_active = 1
+ * dl_throttled = 1
+ * dl_defer_armed = 1
+ * dl_defer_running = 0
+ * dl_defer_idle = 1
+ *
+ * [D] - running
+ * dl_server_active = 1
+ * dl_throttled = 0
+ * dl_defer_armed = 0
+ * dl_defer_running = 1
+ * dl_defer_idle = 0
+ *
+ * [E] - replenish-wait
+ * dl_server_active = 1
+ * dl_throttled = 1
+ * dl_defer_armed = 0
+ * dl_defer_running = 1
+ * dl_defer_idle = 0
+ *
+ *
+ * [1] A->B, A->D
+ * dl_server_start()
+ * dl_server_active = 1;
+ * enqueue_dl_entity()
+ * update_dl_entity(WAKEUP)
+ * if (!dl_defer_running)
+ * dl_defer_armed = 1;
+ * dl_throttled = 1;
+ * if (dl_throttled && start_dl_timer())
+ * return;
+ * // start server into waiting for zero-laxity
+ * __enqueue_dl_entity();
+ * // start running
+ *
+ * // deplete server runtime from client-class
+ * [2] B->B, C->B, E->B
+ * dl_server_update()
+ * update_curr_dl_se()
+ * if (dl_defer_idle)
+ * dl_defer_idle = 0;
+ * if (dl_defer && dl_throttled && dl_runtime_exceeded())
+ * dl_defer_running = 0;
+ * hrtimer_try_to_cancel(); // stop timer
+ * replenish_dl_new_period()
+ * // fwd period
+ * dl_throttled = 1;
+ * dl_defer_armed = 1;
+ * start_dl_timer(); // restart timer
+ * // back into waiting for zero-laxity
+ *
+ * // timer actually fires means we have runtime
+ * [3] B->D
+ * dl_server_timer()
+ * if (dl_defer_armed)
+ * dl_defer_running = 1;
+ * enqueue_dl_entity(REPLENISH)
+ * replenish_dl_entity()
+ * // fwd period
+ * if (dl_throttled)
+ * dl_throttled = 0;
+ * if (dl_defer_armed)
+ * dl_defer_armed = 0;
+ * __enqueue_dl_entity();
+ * // goto [4]
+ *
+ * // schedule server
+ * [4] D->A
+ * pick_task_dl()
+ * p = server_pick_task();
+ * if (!p)
+ * dl_server_stop()
+ * dequeue_dl_entity();
+ * hrtimer_try_to_cancel();
+ * dl_defer_armed = 0;
+ * dl_throttled = 0;
+ * dl_server_active = 0;
+ * // goto [1]
+ * return p;
+ *
+ * // server running
+ * [5] D->E
+ * update_curr_dl_se()
+ * if (dl_runtime_exceeded())
+ * dl_throttled = 1;
+ * dequeue_dl_entity();
+ * start_dl_timer();
+ * // replenish-timer
+ *
+ * // server exchausted
+ * [6] E->D
+ * dl_server_timer()
+ * enqueue_dl_entity(REPLENISH)
+ * replenish_dl_entity()
+ * fwd-period
+ * if (dl_throttled)
+ * dl_throttled = 0;
+ * __enqueue_dl_entity();
+ * // goto [4]
+ *
+ * // deplete server runtime from idle
+ * [7] B->C, C->C
+ * dl_server_update_idle()
+ * update_curr_dl_se()
+ * if (dl_defer && dl_throttled && dl_runtime_exceeded())
+ * if (dl_defer_idle)
+ * return;
+ * dl_defer_running = 0;
+ * hrtimer_try_to_cancel();
+ * replenish_dl_new_period()
+ * // fwd period
+ * dl_throttled = 1;
+ * dl_defer_armed = 1;
+ * dl_defer_idle = 1;
+ * start_dl_timer(); // restart timer
+ *
+ * // stop idle server
+ * [8] C->A
+ * dl_server_timer()
+ * if (dl_defer_idle)
+ * dl_server_stop();
+ *
+ *
+ * digraph dl_server {
+ * "A:init" -> "B:zero_laxity-wait" [label="1:dl_server_start"]
+ * "A:init" -> "D:running" [label="1:dl_server_start"]
+ * "B:zero_laxity-wait" -> "B:zero_laxity-wait" [label="2:dl_server_update"]
+ * "B:zero_laxity-wait" -> "C:idle-wait" [label="7:dl_server_update_idle"]
+ * "B:zero_laxity-wait" -> "D:running" [label="3:dl_server_timer"]
+ * "C:idle-wait" -> "C:idle-wait" [label="7:dl_server_update_idle"]
+ * "C:idle-wait" -> "B:zero_laxity-wait" [label="2:dl_server_update"]
+ * "C:idle-wait" -> "A:init" [label="8:dl_server_timer"]
+ * "D:running" -> "A:init" [label="4:pick_task_dl"]
+ * "D:running" -> "E:replenish-wait" [label="5:update_curr_dl_se"]
+ * "E:replenish-wait" -> "B:zero_laxity-wait" [label="2:dl_server_update"]
+ * "E:replenish-wait" -> "D:running" [label="6:dl_server_timer"]
+ * }
+ *
+ *
+ * Notes:
+ *
+ * - When there are fair tasks running the most likely loop is [2]->[2].
+ * the dl_server never actually runs, the timer never fires.
+ *
+ * - When there is actual fair starvation; the timer fires and starts the
+ * dl_server. This will then throttle and replenish like a normal DL
+ * task. Notably it will not 'defer' again.
+ *
+ * - When idle it will push the activation forward once, and then wait
+ * for the timer to hit or a non-idle update to restart things.
+ */
void dl_server_start(struct sched_dl_entity *dl_se)
{
struct rq *rq = dl_se->rq;
@@ -1590,6 +1818,7 @@ void dl_server_stop(struct sched_dl_enti
hrtimer_try_to_cancel(&dl_se->dl_timer);
dl_se->dl_defer_armed = 0;
dl_se->dl_throttled = 0;
+ dl_se->dl_defer_idle = 0;
dl_se->dl_server_active = 0;
}
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [RFC PATCH] sched/deadline: Avoid dl_server boosting with expired deadline
2025-10-31 15:41 ` Gabriele Monaco
2025-10-31 15:44 ` Peter Zijlstra
@ 2025-11-01 0:08 ` Peter Zijlstra
2025-11-01 8:43 ` Gabriele Monaco
1 sibling, 1 reply; 26+ messages in thread
From: Peter Zijlstra @ 2025-11-01 0:08 UTC (permalink / raw)
To: Gabriele Monaco
Cc: Juri Lelli, linux-kernel, Ingo Molnar, Clark Williams, arighi
On Fri, Oct 31, 2025 at 04:41:22PM +0100, Gabriele Monaco wrote:
> On Fri, 2025-10-31 at 16:20 +0100, Peter Zijlstra wrote:
> > On Fri, Oct 31, 2025 at 02:24:17PM +0100, Gabriele Monaco wrote:
> > >
> > > Different scenario if I have the CPU busy with other tasks (e.g. RT
> > > policies), there I can see the server stopping and starting again.
> > > After I do this I seem to get a different behaviour (even some boosting
> > > after idle), I'm trying to understand what's going on.
> > >
>
> After running some heavy RT workload (stress-ng --cpu 10 --sched rr) I do see
> the server stopping and starting as the models would expect, but somehow it's
> always boosting as soon as it's started.
>
> Apparently dl_defer_running is always 1 in that scenario. Perhaps running idle
> counts as running something too, so it never defers. But I can't really see how
> this happens..
The transition [4], will retain dl_defer_running, such that a timely
re-start of the dl_server can immediately run again.
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [RFC PATCH] sched/deadline: Avoid dl_server boosting with expired deadline
2025-11-01 0:08 ` [RFC PATCH] sched/deadline: Avoid dl_server boosting with expired deadline Peter Zijlstra
@ 2025-11-01 8:43 ` Gabriele Monaco
0 siblings, 0 replies; 26+ messages in thread
From: Gabriele Monaco @ 2025-11-01 8:43 UTC (permalink / raw)
To: Peter Zijlstra
Cc: Juri Lelli, linux-kernel, Ingo Molnar, Clark Williams, arighi
2025-11-01T00:08:37Z Peter Zijlstra <peterz@infradead.org>:
> On Fri, Oct 31, 2025 at 04:41:22PM +0100, Gabriele Monaco wrote:
>> On Fri, 2025-10-31 at 16:20 +0100, Peter Zijlstra wrote:
>>> On Fri, Oct 31, 2025 at 02:24:17PM +0100, Gabriele Monaco wrote:
>>>>
>>>> Different scenario if I have the CPU busy with other tasks (e.g. RT
>>>> policies), there I can see the server stopping and starting again.
>>>> After I do this I seem to get a different behaviour (even some boosting
>>>> after idle), I'm trying to understand what's going on.
>>>>
>>
>> After running some heavy RT workload (stress-ng --cpu 10 --sched rr) I do see
>> the server stopping and starting as the models would expect, but somehow it's
>> always boosting as soon as it's started.
>>
>> Apparently dl_defer_running is always 1 in that scenario. Perhaps running idle
>> counts as running something too, so it never defers. But I can't really see how
>> this happens..
>
> The transition [4], will retain dl_defer_running, such that a timely
> re-start of the dl_server can immediately run again.
Alright I worded it poorly. As far as I understand, what you mentioned is desired behaviour when handling starvation. We don't defer and start the next period boosting.
What I was observing was the server staying running indefinitely.
I run a test with 5s of RR stress-ng and 30s of mostly idle DL workload on a clean VM. I expect boosting only during the first 5 seconds, but I see it also after, where there was clearly no starvation (system was idle, probably a bit hard to see from the trace I shared).
Thanks for the updated patch, I'll try that and see how it goes.
Gabriele
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [RFC PATCH] sched/deadline: Avoid dl_server boosting with expired deadline
2025-11-01 0:00 ` Peter Zijlstra
@ 2025-11-11 9:58 ` Gabriele Monaco
2025-11-11 11:17 ` Peter Zijlstra
2025-11-11 11:37 ` [tip: sched/core] sched/deadline: Fix dl_server stop condition tip-bot2 for Peter Zijlstra
1 sibling, 1 reply; 26+ messages in thread
From: Gabriele Monaco @ 2025-11-11 9:58 UTC (permalink / raw)
To: Peter Zijlstra
Cc: Juri Lelli, linux-kernel, Ingo Molnar, Clark Williams, arighi
On Sat, 2025-11-01 at 01:00 +0100, Peter Zijlstra wrote:
> On Fri, Oct 31, 2025 at 04:44:55PM +0100, Peter Zijlstra wrote:
>
> > Anyway, back to noodling on how to make it stop on idle :-)
>
> This seems to behave (at least, it did before the cleanup).
>
> It has the fancy comment, ascii-art and everything. Hopefully we'll all
> get less confused when looking at this in the future.
>
I tested the 3 patches from your tree and I confirm the server behaves as
expected in my test case:
* it properly stops and restarts
* it doesn't boost after deadline or consumed runtime
I'm going to write a model like the one you drew to catch if there are issues
with boosting when not necessary.
From the traces I see the server stays in the state running until the end of my
test (30s) although the CPU should be busy only for the first 5s.
I see things like this, where the server starts as running, bootsts and stops
(while scheduling out, not in the timer):
<idle>-0 11.412790: sched_dl_server_start: comm=server pid=-9 runtime=34227068 deadline=12209337602 yielded=0
<idle>-0 11.412795: sched_wakeup: ksoftirqd/9:94 [120] CPU:009
<idle>-0 11.412802: bprint: pick_task_dl: Picked task 94 (running 1)
<idle>-0 11.412809: event_throttle: -9: armed x sched_switch_in -> running (final)
<idle>-0 11.412809: sched_switch: swapper/9:0 [120] R ==> ksoftirqd/9:94 [120]
ksoftirqd/9-94 11.412833: event_throttle: -9: running x sched_switch_out -> preempted
ksoftirqd/9-94 11.412834: sched_dl_server_stop: comm=server pid=-9 runtime=34190225 deadline=12209337602 yielded=0
ksoftirqd/9-94 11.412840: sched_switch: ksoftirqd/9:94 [120] S ==> irq/50-virtio1:213 [49]
Is there a reason why dl_server_stop() doesn't reset the running flag? In this
case if the CPU is only running fair tasks in bursts (like ksoftirqd), it will
continue boosting them and stopping the server when they go to sleep, wont' it?
Thanks,
Gabriele
> ---
> include/linux/sched.h | 15 +--
> kernel/sched/deadline.c | 233
> +++++++++++++++++++++++++++++++++++++++++++++++-
> 2 files changed, 240 insertions(+), 8 deletions(-)
>
> --- a/include/linux/sched.h
> +++ b/include/linux/sched.h
> @@ -685,20 +685,22 @@ struct sched_dl_entity {
> *
> * @dl_server tells if this is a server entity.
> *
> - * @dl_defer tells if this is a deferred or regular server. For
> - * now only defer server exists.
> - *
> - * @dl_defer_armed tells if the deferrable server is waiting
> - * for the replenishment timer to activate it.
> - *
> * @dl_server_active tells if the dlserver is active(started).
> * dlserver is started on first cfs enqueue on an idle runqueue
> * and is stopped when a dequeue results in 0 cfs tasks on the
> * runqueue. In other words, dlserver is active only when cpu's
> * runqueue has atleast one cfs task.
> *
> + * @dl_defer tells if this is a deferred or regular server. For
> + * now only defer server exists.
> + *
> + * @dl_defer_armed tells if the deferrable server is waiting
> + * for the replenishment timer to activate it.
> + *
> * @dl_defer_running tells if the deferrable server is actually
> * running, skipping the defer phase.
> + *
> + * @dl_defer_idle tracks idle state
> */
> unsigned int dl_throttled : 1;
> unsigned int dl_yielded : 1;
> @@ -709,6 +711,7 @@ struct sched_dl_entity {
> unsigned int dl_defer : 1;
> unsigned int dl_defer_armed : 1;
> unsigned int dl_defer_running : 1;
> + unsigned int dl_defer_idle : 1;
>
> /*
> * Bandwidth enforcement timer. Each -deadline task has its
> --- a/kernel/sched/deadline.c
> +++ b/kernel/sched/deadline.c
> @@ -1173,6 +1173,11 @@ static enum hrtimer_restart dl_server_ti
> */
> rq->curr->sched_class->update_curr(rq);
>
> + if (dl_se->dl_defer_idle) {
> + dl_server_stop(dl_se);
> + return HRTIMER_NORESTART;
> + }
> +
> if (dl_se->dl_defer_armed) {
> /*
> * First check if the server could consume runtime in
> background.
> @@ -1420,10 +1425,11 @@ s64 dl_scaled_delta_exec(struct rq *rq,
> }
>
> static inline void
> -update_stats_dequeue_dl(struct dl_rq *dl_rq, struct sched_dl_entity *dl_se,
> - int flags);
> +update_stats_dequeue_dl(struct dl_rq *dl_rq, struct sched_dl_entity *dl_se,
> int flags);
> +
> static void update_curr_dl_se(struct rq *rq, struct sched_dl_entity *dl_se,
> s64 delta_exec)
> {
> + bool idle = rq->curr == rq->idle;
> s64 scaled_delta_exec;
>
> if (unlikely(delta_exec <= 0)) {
> @@ -1444,6 +1450,9 @@ static void update_curr_dl_se(struct rq
>
> dl_se->runtime -= scaled_delta_exec;
>
> + if (dl_se->dl_defer_idle && !idle)
> + dl_se->dl_defer_idle = 0;
> +
> /*
> * The fair server can consume its runtime while throttled (not
> queued/
> * running as regular CFS).
> @@ -1454,6 +1463,29 @@ static void update_curr_dl_se(struct rq
> */
> if (dl_se->dl_defer && dl_se->dl_throttled &&
> dl_runtime_exceeded(dl_se)) {
> /*
> + * Non-servers would never get time accounted while
> throttled.
> + */
> + WARN_ON_ONCE(!dl_server(dl_se));
> +
> + /*
> + * While the server is marked idle, do not push out the
> + * activation further, instead wait for the period timer
> + * to lapse and stop the server.
> + */
> + if (dl_se->dl_defer_idle && idle) {
> + /*
> + * The timer is at the zero-laxity point, this means
> + * dl_server_stop() / dl_server_start() can happen
> + * while now < deadline. This means
> update_dl_entity()
> + * will not replenish. Additionally start_dl_timer()
> + * will be set for 'deadline - runtime'. Negative
> + * runtime will not do.
> + */
> + dl_se->runtime = 0;
> + return;
> + }
> +
> + /*
> * If the server was previously activated - the starving
> condition
> * took place, it this point it went away because the fair
> scheduler
> * was able to get runtime in background. So return to the
> initial
> @@ -1465,6 +1497,9 @@ static void update_curr_dl_se(struct rq
>
> replenish_dl_new_period(dl_se, dl_se->rq);
>
> + if (idle)
> + dl_se->dl_defer_idle = 1;
> +
> /*
> * Not being able to start the timer seems problematic. If it
> could not
> * be started for whatever reason, we need to "unthrottle"
> the DL server
> @@ -1560,6 +1595,199 @@ void dl_server_update(struct sched_dl_en
> update_curr_dl_se(dl_se->rq, dl_se, delta_exec);
> }
>
> +/*
> + * dl_server && dl_defer:
> + *
> + * 6
> + * +--------------------+
> + * v |
> + * +-------------+ 4 +-----------+ 5 +------------------+
> + * +-> | A:init | <--- | D:running | -----> | E:replenish-wait |
> + * | +-------------+ +-----------+ +------------------+
> + * | | | 1 ^ ^ |
> + * | | 1 +----------+ | 3 |
> + * | v | |
> + * | +--------------------------------+ 2 |
> + * | | | ----+ |
> + * | 8 | B:zero_laxity-wait | | |
> + * | | | <---+ |
> + * | +--------------------------------+ |
> + * | | ^ ^ 2 |
> + * | | 7 | 2 +--------------------+
> + * | v |
> + * | +-------------+ |
> + * +-- | C:idle-wait | -+
> + * +-------------+
> + * ^ 7 |
> + * +---------+
> + *
> + *
> + * [A] - init
> + * dl_server_active = 0
> + * dl_throttled = 0
> + * dl_defer_armed = 0
> + * dl_defer_running = 0/1
> + * dl_defer_idle = 0
> + *
> + * [B] - zero_laxity-wait
> + * dl_server_active = 1
> + * dl_throttled = 1
> + * dl_defer_armed = 1
> + * dl_defer_running = 0
> + * dl_defer_idle = 0
> + *
> + * [C] - idle-wait
> + * dl_server_active = 1
> + * dl_throttled = 1
> + * dl_defer_armed = 1
> + * dl_defer_running = 0
> + * dl_defer_idle = 1
> + *
> + * [D] - running
> + * dl_server_active = 1
> + * dl_throttled = 0
> + * dl_defer_armed = 0
> + * dl_defer_running = 1
> + * dl_defer_idle = 0
> + *
> + * [E] - replenish-wait
> + * dl_server_active = 1
> + * dl_throttled = 1
> + * dl_defer_armed = 0
> + * dl_defer_running = 1
> + * dl_defer_idle = 0
> + *
> + *
> + * [1] A->B, A->D
> + * dl_server_start()
> + * dl_server_active = 1;
> + * enqueue_dl_entity()
> + * update_dl_entity(WAKEUP)
> + * if (!dl_defer_running)
> + * dl_defer_armed = 1;
> + * dl_throttled = 1;
> + * if (dl_throttled && start_dl_timer())
> + * return;
> + * // start server into waiting for zero-laxity
> + * __enqueue_dl_entity();
> + * // start running
> + *
> + * // deplete server runtime from client-class
> + * [2] B->B, C->B, E->B
> + * dl_server_update()
> + * update_curr_dl_se()
> + * if (dl_defer_idle)
> + * dl_defer_idle = 0;
> + * if (dl_defer && dl_throttled && dl_runtime_exceeded())
> + * dl_defer_running = 0;
> + * hrtimer_try_to_cancel(); // stop timer
> + * replenish_dl_new_period()
> + * // fwd period
> + * dl_throttled = 1;
> + * dl_defer_armed = 1;
> + * start_dl_timer(); // restart timer
> + * // back into waiting for zero-laxity
> + *
> + * // timer actually fires means we have runtime
> + * [3] B->D
> + * dl_server_timer()
> + * if (dl_defer_armed)
> + * dl_defer_running = 1;
> + * enqueue_dl_entity(REPLENISH)
> + * replenish_dl_entity()
> + * // fwd period
> + * if (dl_throttled)
> + * dl_throttled = 0;
> + * if (dl_defer_armed)
> + * dl_defer_armed = 0;
> + * __enqueue_dl_entity();
> + * // goto [4]
> + *
> + * // schedule server
> + * [4] D->A
> + * pick_task_dl()
> + * p = server_pick_task();
> + * if (!p)
> + * dl_server_stop()
> + * dequeue_dl_entity();
> + * hrtimer_try_to_cancel();
> + * dl_defer_armed = 0;
> + * dl_throttled = 0;
> + * dl_server_active = 0;
> + * // goto [1]
> + * return p;
> + *
> + * // server running
> + * [5] D->E
> + * update_curr_dl_se()
> + * if (dl_runtime_exceeded())
> + * dl_throttled = 1;
> + * dequeue_dl_entity();
> + * start_dl_timer();
> + * // replenish-timer
> + *
> + * // server exchausted
> + * [6] E->D
> + * dl_server_timer()
> + * enqueue_dl_entity(REPLENISH)
> + * replenish_dl_entity()
> + * fwd-period
> + * if (dl_throttled)
> + * dl_throttled = 0;
> + * __enqueue_dl_entity();
> + * // goto [4]
> + *
> + * // deplete server runtime from idle
> + * [7] B->C, C->C
> + * dl_server_update_idle()
> + * update_curr_dl_se()
> + * if (dl_defer && dl_throttled && dl_runtime_exceeded())
> + * if (dl_defer_idle)
> + * return;
> + * dl_defer_running = 0;
> + * hrtimer_try_to_cancel();
> + * replenish_dl_new_period()
> + * // fwd period
> + * dl_throttled = 1;
> + * dl_defer_armed = 1;
> + * dl_defer_idle = 1;
> + * start_dl_timer(); // restart timer
> + *
> + * // stop idle server
> + * [8] C->A
> + * dl_server_timer()
> + * if (dl_defer_idle)
> + * dl_server_stop();
> + *
> + *
> + * digraph dl_server {
> + * "A:init" -> "B:zero_laxity-wait" [label="1:dl_server_start"]
> + * "A:init" -> "D:running" [label="1:dl_server_start"]
> + * "B:zero_laxity-wait" -> "B:zero_laxity-wait"
> [label="2:dl_server_update"]
> + * "B:zero_laxity-wait" -> "C:idle-wait"
> [label="7:dl_server_update_idle"]
> + * "B:zero_laxity-wait" -> "D:running" [label="3:dl_server_timer"]
> + * "C:idle-wait" -> "C:idle-wait"
> [label="7:dl_server_update_idle"]
> + * "C:idle-wait" -> "B:zero_laxity-wait"
> [label="2:dl_server_update"]
> + * "C:idle-wait" -> "A:init" [label="8:dl_server_timer"]
> + * "D:running" -> "A:init" [label="4:pick_task_dl"]
> + * "D:running" -> "E:replenish-wait"
> [label="5:update_curr_dl_se"]
> + * "E:replenish-wait" -> "B:zero_laxity-wait"
> [label="2:dl_server_update"]
> + * "E:replenish-wait" -> "D:running" [label="6:dl_server_timer"]
> + * }
> + *
> + *
> + * Notes:
> + *
> + * - When there are fair tasks running the most likely loop is [2]->[2].
> + * the dl_server never actually runs, the timer never fires.
> + *
> + * - When there is actual fair starvation; the timer fires and starts the
> + * dl_server. This will then throttle and replenish like a normal DL
> + * task. Notably it will not 'defer' again.
> + *
> + * - When idle it will push the activation forward once, and then wait
> + * for the timer to hit or a non-idle update to restart things.
> + */
> void dl_server_start(struct sched_dl_entity *dl_se)
> {
> struct rq *rq = dl_se->rq;
> @@ -1590,6 +1818,7 @@ void dl_server_stop(struct sched_dl_enti
> hrtimer_try_to_cancel(&dl_se->dl_timer);
> dl_se->dl_defer_armed = 0;
> dl_se->dl_throttled = 0;
> + dl_se->dl_defer_idle = 0;
> dl_se->dl_server_active = 0;
> }
>
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [RFC PATCH] sched/deadline: Avoid dl_server boosting with expired deadline
2025-11-11 9:58 ` Gabriele Monaco
@ 2025-11-11 11:17 ` Peter Zijlstra
2025-11-11 11:24 ` Peter Zijlstra
0 siblings, 1 reply; 26+ messages in thread
From: Peter Zijlstra @ 2025-11-11 11:17 UTC (permalink / raw)
To: Gabriele Monaco
Cc: Juri Lelli, linux-kernel, Ingo Molnar, Clark Williams, arighi
On Tue, Nov 11, 2025 at 10:58:51AM +0100, Gabriele Monaco wrote:
> Is there a reason why dl_server_stop() doesn't reset the running flag?
Yes. Suppose we're boosting a task, and that task has a small sleep
(mutex or whatever). The dl_server won't have anything to actually boost
and will stop itself, clearing running (if dl_server_stop() were to
clear running).
Then if the initial fair task wakes up again, we'll (re)start the
dl_server, but since !running, it will go do the whole defer/zero-laxity
thing again, making out task wait for a whole period until it can run
again.
Conversely (dl_server_stop(), like now, does *not* clear running), if
running is retained, the dl_server is able to immediately resume its
still valid runtime.
Put otherwise, a fair task that always runs and a fair task that has
micro-sleeps should more or less behave the same. We should not punish a
micro-sleep with a whole second delay.
Now, if the sleep is long, we'll find the deadline is in the past and
then it pushes the activation forward and we go ... Oooh to E.
Let me go draw that line in the diagram.
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [RFC PATCH] sched/deadline: Avoid dl_server boosting with expired deadline
2025-11-11 11:17 ` Peter Zijlstra
@ 2025-11-11 11:24 ` Peter Zijlstra
0 siblings, 0 replies; 26+ messages in thread
From: Peter Zijlstra @ 2025-11-11 11:24 UTC (permalink / raw)
To: Gabriele Monaco
Cc: Juri Lelli, linux-kernel, Ingo Molnar, Clark Williams, arighi
On Tue, Nov 11, 2025 at 12:17:17PM +0100, Peter Zijlstra wrote:
> On Tue, Nov 11, 2025 at 10:58:51AM +0100, Gabriele Monaco wrote:
>
> > Is there a reason why dl_server_stop() doesn't reset the running flag?
>
> Yes. Suppose we're boosting a task, and that task has a small sleep
> (mutex or whatever). The dl_server won't have anything to actually boost
> and will stop itself, clearing running (if dl_server_stop() were to
> clear running).
>
> Then if the initial fair task wakes up again, we'll (re)start the
> dl_server, but since !running, it will go do the whole defer/zero-laxity
> thing again, making out task wait for a whole period until it can run
> again.
>
> Conversely (dl_server_stop(), like now, does *not* clear running), if
> running is retained, the dl_server is able to immediately resume its
> still valid runtime.
>
> Put otherwise, a fair task that always runs and a fair task that has
> micro-sleeps should more or less behave the same. We should not punish a
> micro-sleep with a whole second delay.
>
> Now, if the sleep is long, we'll find the deadline is in the past and
> then it pushes the activation forward and we go ... Oooh to E.
Ah, no, replenlish_dl_new_period() sets defer_armed = 1, so
start_dl_timer() from enqueue_dl_entity() will end up in zero-laxity
wait.
Damn, so much notes and I still get it wrong :-(
^ permalink raw reply [flat|nested] 26+ messages in thread
* [tip: sched/core] sched/deadline: Fix dl_server stop condition
2025-11-01 0:00 ` Peter Zijlstra
2025-11-11 9:58 ` Gabriele Monaco
@ 2025-11-11 11:37 ` tip-bot2 for Peter Zijlstra
1 sibling, 0 replies; 26+ messages in thread
From: tip-bot2 for Peter Zijlstra @ 2025-11-11 11:37 UTC (permalink / raw)
To: linux-tip-commits
Cc: Gabriele Monaco, Peter Zijlstra (Intel), x86, linux-kernel
The following commit has been merged into the sched/core branch of tip:
Commit-ID: f5a538c07df26f5c601e41f7b9c7ade3e1e75803
Gitweb: https://git.kernel.org/tip/f5a538c07df26f5c601e41f7b9c7ade3e1e75803
Author: Peter Zijlstra <peterz@infradead.org>
AuthorDate: Fri, 31 Oct 2025 13:54:24 +01:00
Committer: Peter Zijlstra <peterz@infradead.org>
CommitterDate: Tue, 11 Nov 2025 12:33:39 +01:00
sched/deadline: Fix dl_server stop condition
Gabriel reported that the dl_server doesn't stop as expected.
The problem was found to be the fact that idle time and fair runtime are
treated equally. Both will count towards dl_server runtime and push the
activation forwards when it is in the zero-laxity wait state.
Notably:
dl_server_update_idle()
update_curr_dl_se()
if (dl_defer && dl_throttled && dl_runtime_exceeded())
hrtimer_try_to_cancel(); // stop timer
replenish_dl_new_period()
deadline = now + dl_deadline; // fwd period
runtime = dl_runtime;
start_dl_timer(); // restart timer
And while we do want idle time accounted towards the *current* activation of
the dl_server -- after all, a fair task could've ran if we had any -- we don't
necessarily want idle time to cause or push forward an activation.
Introduce dl_defer_idle to make this distinction. It will be set once idle time
pushed the activation forward, once set idle time will only be allowed to
consume any runtime but not push the activation. This will then cause
dl_server_timer() to fire, which will stop the dl_server.
Any non-idle time accounting during this phase will clear dl_defer_idle, so
only a full period of idle will cause the dl_server to stop.
Reported-by: Gabriele Monaco <gmonaco@redhat.com>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Link: https://patch.msgid.link/20251101000057.GA2184199@noisy.programming.kicks-ass.net
---
include/linux/sched.h | 15 +++++++++------
kernel/sched/deadline.c | 40 ++++++++++++++++++++++++++++++++++++++--
2 files changed, 47 insertions(+), 8 deletions(-)
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 0757647..bb436ee 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -685,20 +685,22 @@ struct sched_dl_entity {
*
* @dl_server tells if this is a server entity.
*
- * @dl_defer tells if this is a deferred or regular server. For
- * now only defer server exists.
- *
- * @dl_defer_armed tells if the deferrable server is waiting
- * for the replenishment timer to activate it.
- *
* @dl_server_active tells if the dlserver is active(started).
* dlserver is started on first cfs enqueue on an idle runqueue
* and is stopped when a dequeue results in 0 cfs tasks on the
* runqueue. In other words, dlserver is active only when cpu's
* runqueue has atleast one cfs task.
*
+ * @dl_defer tells if this is a deferred or regular server. For
+ * now only defer server exists.
+ *
+ * @dl_defer_armed tells if the deferrable server is waiting
+ * for the replenishment timer to activate it.
+ *
* @dl_defer_running tells if the deferrable server is actually
* running, skipping the defer phase.
+ *
+ * @dl_defer_idle tracks idle state
*/
unsigned int dl_throttled : 1;
unsigned int dl_yielded : 1;
@@ -709,6 +711,7 @@ struct sched_dl_entity {
unsigned int dl_defer : 1;
unsigned int dl_defer_armed : 1;
unsigned int dl_defer_running : 1;
+ unsigned int dl_defer_idle : 1;
/*
* Bandwidth enforcement timer. Each -deadline task has its
diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c
index ece25ca..8307f24 100644
--- a/kernel/sched/deadline.c
+++ b/kernel/sched/deadline.c
@@ -1173,6 +1173,11 @@ static enum hrtimer_restart dl_server_timer(struct hrtimer *timer, struct sched_
*/
rq->donor->sched_class->update_curr(rq);
+ if (dl_se->dl_defer_idle) {
+ dl_server_stop(dl_se);
+ return HRTIMER_NORESTART;
+ }
+
if (dl_se->dl_defer_armed) {
/*
* First check if the server could consume runtime in background.
@@ -1420,10 +1425,11 @@ s64 dl_scaled_delta_exec(struct rq *rq, struct sched_dl_entity *dl_se, s64 delta
}
static inline void
-update_stats_dequeue_dl(struct dl_rq *dl_rq, struct sched_dl_entity *dl_se,
- int flags);
+update_stats_dequeue_dl(struct dl_rq *dl_rq, struct sched_dl_entity *dl_se, int flags);
+
static void update_curr_dl_se(struct rq *rq, struct sched_dl_entity *dl_se, s64 delta_exec)
{
+ bool idle = rq->curr == rq->idle;
s64 scaled_delta_exec;
if (unlikely(delta_exec <= 0)) {
@@ -1444,6 +1450,9 @@ static void update_curr_dl_se(struct rq *rq, struct sched_dl_entity *dl_se, s64
dl_se->runtime -= scaled_delta_exec;
+ if (dl_se->dl_defer_idle && !idle)
+ dl_se->dl_defer_idle = 0;
+
/*
* The fair server can consume its runtime while throttled (not queued/
* running as regular CFS).
@@ -1454,6 +1463,29 @@ static void update_curr_dl_se(struct rq *rq, struct sched_dl_entity *dl_se, s64
*/
if (dl_se->dl_defer && dl_se->dl_throttled && dl_runtime_exceeded(dl_se)) {
/*
+ * Non-servers would never get time accounted while throttled.
+ */
+ WARN_ON_ONCE(!dl_server(dl_se));
+
+ /*
+ * While the server is marked idle, do not push out the
+ * activation further, instead wait for the period timer
+ * to lapse and stop the server.
+ */
+ if (dl_se->dl_defer_idle && idle) {
+ /*
+ * The timer is at the zero-laxity point, this means
+ * dl_server_stop() / dl_server_start() can happen
+ * while now < deadline. This means update_dl_entity()
+ * will not replenish. Additionally start_dl_timer()
+ * will be set for 'deadline - runtime'. Negative
+ * runtime will not do.
+ */
+ dl_se->runtime = 0;
+ return;
+ }
+
+ /*
* If the server was previously activated - the starving condition
* took place, it this point it went away because the fair scheduler
* was able to get runtime in background. So return to the initial
@@ -1465,6 +1497,9 @@ static void update_curr_dl_se(struct rq *rq, struct sched_dl_entity *dl_se, s64
replenish_dl_new_period(dl_se, dl_se->rq);
+ if (idle)
+ dl_se->dl_defer_idle = 1;
+
/*
* Not being able to start the timer seems problematic. If it could not
* be started for whatever reason, we need to "unthrottle" the DL server
@@ -1590,6 +1625,7 @@ void dl_server_stop(struct sched_dl_entity *dl_se)
hrtimer_try_to_cancel(&dl_se->dl_timer);
dl_se->dl_defer_armed = 0;
dl_se->dl_throttled = 0;
+ dl_se->dl_defer_idle = 0;
dl_se->dl_server_active = 0;
}
^ permalink raw reply [flat|nested] 26+ messages in thread
* [tip: sched/core] sched/deadline: Fix dl_server time accounting
2025-10-20 14:11 ` Peter Zijlstra
2025-10-22 10:11 ` Gabriele Monaco
@ 2025-11-11 11:37 ` tip-bot2 for Peter Zijlstra
1 sibling, 0 replies; 26+ messages in thread
From: tip-bot2 for Peter Zijlstra @ 2025-11-11 11:37 UTC (permalink / raw)
To: linux-tip-commits; +Cc: Peter Zijlstra (Intel), x86, linux-kernel
The following commit has been merged into the sched/core branch of tip:
Commit-ID: e636ffb9e31b4f7dde7fef5358669266b9ce02ec
Gitweb: https://git.kernel.org/tip/e636ffb9e31b4f7dde7fef5358669266b9ce02ec
Author: Peter Zijlstra <peterz@infradead.org>
AuthorDate: Mon, 20 Oct 2025 16:15:05 +02:00
Committer: Peter Zijlstra <peterz@infradead.org>
CommitterDate: Tue, 11 Nov 2025 12:33:38 +01:00
sched/deadline: Fix dl_server time accounting
The dl_server time accounting code is a little odd. The normal scheduler
pattern is to update curr before doing something, such that the old state is
fully accounted before changing state.
Notably, the dl_server_timer() needs to propagate the current time accounting
since the current task could be ran by dl_server and thus this can affect
dl_se->runtime. Similarly for dl_server_start().
And since the (deferred) dl_server wants idle time accounted, rework
sched_idle_class time accounting to be more like all the others.
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Link: https://patch.msgid.link/20251020141130.GJ3245006@noisy.programming.kicks-ass.net
---
kernel/sched/deadline.c | 40 +++++++++++++++-------------------------
kernel/sched/fair.c | 9 ++-------
kernel/sched/idle.c | 16 +++++++++++++++-
kernel/sched/sched.h | 3 +--
4 files changed, 33 insertions(+), 35 deletions(-)
diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c
index 13112c6..ece25ca 100644
--- a/kernel/sched/deadline.c
+++ b/kernel/sched/deadline.c
@@ -1166,8 +1166,12 @@ static enum hrtimer_restart dl_server_timer(struct hrtimer *timer, struct sched_
sched_clock_tick();
update_rq_clock(rq);
- if (!dl_se->dl_runtime)
- return HRTIMER_NORESTART;
+ /*
+ * Make sure current has propagated its pending runtime into
+ * any relevant server through calling dl_server_update() and
+ * friends.
+ */
+ rq->donor->sched_class->update_curr(rq);
if (dl_se->dl_defer_armed) {
/*
@@ -1543,35 +1547,16 @@ throttle:
* as time available for the fair server, avoiding a penalty for the
* rt scheduler that did not consumed that time.
*/
-void dl_server_update_idle_time(struct rq *rq, struct task_struct *p)
+void dl_server_update_idle(struct sched_dl_entity *dl_se, s64 delta_exec)
{
- s64 delta_exec;
-
- if (!rq->fair_server.dl_defer)
- return;
-
- /* no need to discount more */
- if (rq->fair_server.runtime < 0)
- return;
-
- delta_exec = rq_clock_task(rq) - p->se.exec_start;
- if (delta_exec < 0)
- return;
-
- rq->fair_server.runtime -= delta_exec;
-
- if (rq->fair_server.runtime < 0) {
- rq->fair_server.dl_defer_running = 0;
- rq->fair_server.runtime = 0;
- }
-
- p->se.exec_start = rq_clock_task(rq);
+ if (dl_se->dl_server_active && dl_se->dl_runtime && dl_se->dl_defer)
+ update_curr_dl_se(dl_se->rq, dl_se, delta_exec);
}
void dl_server_update(struct sched_dl_entity *dl_se, s64 delta_exec)
{
/* 0 runtime = fair server disabled */
- if (dl_se->dl_runtime)
+ if (dl_se->dl_server_active && dl_se->dl_runtime)
update_curr_dl_se(dl_se->rq, dl_se, delta_exec);
}
@@ -1582,6 +1567,11 @@ void dl_server_start(struct sched_dl_entity *dl_se)
if (!dl_server(dl_se) || dl_se->dl_server_active)
return;
+ /*
+ * Update the current task to 'now'.
+ */
+ rq->donor->sched_class->update_curr(rq);
+
if (WARN_ON_ONCE(!cpu_online(cpu_of(rq))))
return;
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 8d971d4..b4617d6 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -1212,8 +1212,7 @@ static void update_curr(struct cfs_rq *cfs_rq)
* against fair_server such that it can account for this time
* and possibly avoid running this period.
*/
- if (dl_server_active(&rq->fair_server))
- dl_server_update(&rq->fair_server, delta_exec);
+ dl_server_update(&rq->fair_server, delta_exec);
}
account_cfs_rq_runtime(cfs_rq, delta_exec);
@@ -6961,12 +6960,8 @@ enqueue_task_fair(struct rq *rq, struct task_struct *p, int flags)
h_nr_idle = 1;
}
- if (!rq_h_nr_queued && rq->cfs.h_nr_queued) {
- /* Account for idle runtime */
- if (!rq->nr_running)
- dl_server_update_idle_time(rq, rq->curr);
+ if (!rq_h_nr_queued && rq->cfs.h_nr_queued)
dl_server_start(&rq->fair_server);
- }
/* At this point se is NULL and we are at root level*/
add_nr_running(rq, 1);
diff --git a/kernel/sched/idle.c b/kernel/sched/idle.c
index 7fa0b59..1cb7a3d 100644
--- a/kernel/sched/idle.c
+++ b/kernel/sched/idle.c
@@ -452,9 +452,11 @@ static void wakeup_preempt_idle(struct rq *rq, struct task_struct *p, int flags)
resched_curr(rq);
}
+static void update_curr_idle(struct rq *rq);
+
static void put_prev_task_idle(struct rq *rq, struct task_struct *prev, struct task_struct *next)
{
- dl_server_update_idle_time(rq, prev);
+ update_curr_idle(rq);
scx_update_idle(rq, false, true);
}
@@ -496,6 +498,7 @@ dequeue_task_idle(struct rq *rq, struct task_struct *p, int flags)
*/
static void task_tick_idle(struct rq *rq, struct task_struct *curr, int queued)
{
+ update_curr_idle(rq);
}
static void switching_to_idle(struct rq *rq, struct task_struct *p)
@@ -514,6 +517,17 @@ prio_changed_idle(struct rq *rq, struct task_struct *p, u64 oldprio)
static void update_curr_idle(struct rq *rq)
{
+ struct sched_entity *se = &rq->idle->se;
+ u64 now = rq_clock_task(rq);
+ s64 delta_exec;
+
+ delta_exec = now - se->exec_start;
+ if (unlikely(delta_exec <= 0))
+ return;
+
+ se->exec_start = now;
+
+ dl_server_update_idle(&rq->fair_server, delta_exec);
}
/*
diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h
index 5a3cf81..def9ab7 100644
--- a/kernel/sched/sched.h
+++ b/kernel/sched/sched.h
@@ -404,6 +404,7 @@ extern s64 dl_scaled_delta_exec(struct rq *rq, struct sched_dl_entity *dl_se, s6
* naturally thottled to once per period, avoiding high context switch
* workloads from spamming the hrtimer program/cancel paths.
*/
+extern void dl_server_update_idle(struct sched_dl_entity *dl_se, s64 delta_exec);
extern void dl_server_update(struct sched_dl_entity *dl_se, s64 delta_exec);
extern void dl_server_start(struct sched_dl_entity *dl_se);
extern void dl_server_stop(struct sched_dl_entity *dl_se);
@@ -411,8 +412,6 @@ extern void dl_server_init(struct sched_dl_entity *dl_se, struct rq *rq,
dl_server_pick_f pick_task);
extern void sched_init_dl_servers(void);
-extern void dl_server_update_idle_time(struct rq *rq,
- struct task_struct *p);
extern void fair_server_init(struct rq *rq);
extern void __dl_server_attach_root(struct sched_dl_entity *dl_se, struct rq *rq);
extern int dl_server_apply_params(struct sched_dl_entity *dl_se,
^ permalink raw reply [flat|nested] 26+ messages in thread
end of thread, other threads:[~2025-11-11 11:37 UTC | newest]
Thread overview: 26+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-10-07 12:29 [RFC PATCH] sched/deadline: Avoid dl_server boosting with expired deadline Gabriele Monaco
2025-10-14 9:30 ` Gabriele Monaco
2025-10-14 9:54 ` Peter Zijlstra
2025-10-14 10:05 ` Gabriele Monaco
2025-10-14 10:25 ` Peter Zijlstra
2025-10-14 15:32 ` Gabriele Monaco
2025-10-14 16:01 ` Juri Lelli
2025-10-14 19:33 ` Peter Zijlstra
2025-10-15 5:40 ` Juri Lelli
2025-10-20 14:11 ` Peter Zijlstra
2025-10-22 10:11 ` Gabriele Monaco
2025-10-30 18:42 ` Peter Zijlstra
2025-10-31 13:05 ` Peter Zijlstra
2025-10-31 13:24 ` Gabriele Monaco
2025-10-31 15:20 ` Peter Zijlstra
2025-10-31 15:41 ` Gabriele Monaco
2025-10-31 15:44 ` Peter Zijlstra
2025-10-31 15:51 ` Gabriele Monaco
2025-11-01 0:00 ` Peter Zijlstra
2025-11-11 9:58 ` Gabriele Monaco
2025-11-11 11:17 ` Peter Zijlstra
2025-11-11 11:24 ` Peter Zijlstra
2025-11-11 11:37 ` [tip: sched/core] sched/deadline: Fix dl_server stop condition tip-bot2 for Peter Zijlstra
2025-11-01 0:08 ` [RFC PATCH] sched/deadline: Avoid dl_server boosting with expired deadline Peter Zijlstra
2025-11-01 8:43 ` Gabriele Monaco
2025-11-11 11:37 ` [tip: sched/core] sched/deadline: Fix dl_server time accounting tip-bot2 for Peter Zijlstra
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®