[PATCH] sched/psi: Keep average work scheduled for pending notifications

From: Hui Su

Date: Mon Oct 19 2026 - 00:16:47 EST


Unprivileged PSI triggers use the average aggregation worker.
When a threshold is breached during the rate-limit window,
the notification is deferred by setting pending_event.

If pressure stops before the rate limit expires,
psi_avgs_work() can stop rearming because
PSI_STATE_RESCHEDULE is no longer set. The pending event
then remains undelivered until new activity restarts
the worker.

Reuse the trigger walk result when an average update is due.
When the worker runs early, check for pending events because
psi_show() can advance avg_next_update without processing
triggers. Once the notification is delivered and pressure
has stopped, allow the worker to go idle again.

Fixes: d82caa273565 ("sched/psi: Allow unprivileged polling of N*2s period")
Signed-off-by: Hui Su <sh_def@xxxxxxx>
---
Tested on x86_64:
- KASAN KUnit: candidate PSI_AVGS 6/6 passed, including trigger destruction;
refcount tests 4/4 passed, with no KASAN report. Baseline had 3/6 expected
PSI_AVGS regression failures.
- QEMU cgroup v2 with unprivileged `some 5000 4000000`: baseline missed the
second event with `pending=1/rearm=0`; candidate delivered it at 3999.9 ms
with `pending=1/rearm=1`, then cleared the pending event. The second pressure
deltas were 505240/490317 us; both quiet-period deltas were 0.

kernel/sched/psi.c | 30 +++++++++++++++++++++++++-----
1 file changed, 25 insertions(+), 5 deletions(-)

diff --git a/kernel/sched/psi.c b/kernel/sched/psi.c
index 7c53814233345..e17826ac1dbc7 100644
--- a/kernel/sched/psi.c
+++ b/kernel/sched/psi.c
@@ -460,13 +460,14 @@ static u64 window_update(struct psi_window *win, u64 now, u64 value)
return growth;
}

-static void update_triggers(struct psi_group *group, u64 now,
- enum psi_aggregators aggregator)
+static bool update_triggers(struct psi_group *group, u64 now,
+ enum psi_aggregators aggregator)
{
struct psi_trigger *t;
u64 *total = group->total[aggregator];
struct list_head *triggers;
u64 *aggregator_total;
+ bool pending = false;

if (aggregator == PSI_AVGS) {
triggers = &group->avg_triggers;
@@ -506,8 +507,10 @@ static void update_triggers(struct psi_group *group, u64 now,
}
}
/* Limit event signaling to once per window */
- if (now < t->last_event_time + t->win.size)
+ if (now < t->last_event_time + t->win.size) {
+ pending = true;
continue;
+ }

/* Generate an event */
if (cmpxchg(&t->event, 0, 1) == 0) {
@@ -520,6 +523,8 @@ static void update_triggers(struct psi_group *group, u64 now,
/* Reset threshold breach flag once event got generated */
t->pending_event = false;
}
+
+ return pending;
}

static u64 update_averages(struct psi_group *group, u64 now)
@@ -575,11 +580,24 @@ static u64 update_averages(struct psi_group *group, u64 now)
return avg_next_update;
}

+static bool psi_has_pending_avg_events(struct psi_group *group)
+{
+ struct psi_trigger *t;
+
+ list_for_each_entry(t, &group->avg_triggers, node) {
+ if (t->pending_event)
+ return true;
+ }
+
+ return false;
+}
+
static void psi_avgs_work(struct work_struct *work)
{
struct delayed_work *dwork;
struct psi_group *group;
u32 changed_states;
+ bool pending = false;
u64 now;

dwork = to_delayed_work(work);
@@ -598,11 +616,13 @@ static void psi_avgs_work(struct work_struct *work)
* go - see calc_avgs() and missed_periods.
*/
if (now >= group->avg_next_update) {
- update_triggers(group, now, PSI_AVGS);
+ pending = update_triggers(group, now, PSI_AVGS);
group->avg_next_update = update_averages(group, now);
+ } else if (!(changed_states & PSI_STATE_RESCHEDULE)) {
+ pending = psi_has_pending_avg_events(group);
}

- if (changed_states & PSI_STATE_RESCHEDULE) {
+ if ((changed_states & PSI_STATE_RESCHEDULE) || pending) {
schedule_delayed_work(dwork, nsecs_to_jiffies(
group->avg_next_update - now) + 1);
}

base-commit: b99b7f60c5b473d374c68b53753153b4aa9e0702