hrtimer: Use hard expiry when updating timers on the same base
PATCH v3 1/1 hrtimer: Use hard expiry when updating timers on the same base
Rearming a queued timer with nonzero slack can leave the timerqueue out of order. remove_and_enqueue_same_base() checks the new soft expiry against its neighbours' hard expiries, then stores the new hard expiry in the node without requeueing it.
For example, with A at 10 and B at 20, rearming A at 11 with slack 30 passes the neighbour check but leaves A's hard expiry of 41 before B's
20. The same function also caches the soft expiry in base->expires_next when updating or inserting the first timer, giving next-event selection an earlier deadline than the queue head's hard expiry.
Set the timer expiry before handling the queue. Use its stored hard expiry for the in-place ordering check and both updates to base->expires_next.
The early update is safe because remove_and_enqueue_same_base() runs with base->cpu_base->lock held. The lock keeps the queue stable while hrtimer_can_update_in_place() checks the new expiry against both neighbours. If the check fails, timerqueue_linked_del() removes the node without comparing expiry values before it is reinserted.
---
Changes in v3:
- Explain why updating the expiry before handling the queue is safe, as requested by Thomas Gleixner.
Changes in v2:
- Set the timer expiry before handling the queue and use the resulting hard expiry for the in-place update check, as suggested by Peter Zijlstra.
v2: https://lore.kernel.org/r/[email protected]/
v1: https://lore.kernel.org/r/[email protected]/
kernel/time/hrtimer.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-)
kernel/time/hrtimer.c +11 -4
@@ -1263,13 +1263,23 @@ remove_and_enqueue_same_base(struct hrtimer *timer, struct hrtimer_clock_base *b {bool was_first = false; +/* +* Updating the sort key while @timer is queued can temporarily +* make the tree inconsistent. This is safe under cpu_base->lock: +* no other queue operation can observe that state. +* hrtimer_can_update_in_place() either confirms that the new expiry +* fits between the neighbours or timerqueue_linked_del() removes the +* timer without consulting the expiry. +*/ +hrtimer_set_expires_range_ns (timer, expires, delta_ns); + expires =hrtimer_get_expires (timer); +/* Remove it from the timer queue if active */ if (timer->is_queued) { was_first = !timerqueue_linked_prev (&timer->node);/* Try to update in place to avoid the de/enqueue dance */ if (hrtimer_can_update_in_place (timer, base, expires)) { -hrtimer_set_expires_range_ns (timer, expires, delta_ns);trace_hrtimer_start (timer, mode, true);if (was_first) base->expires_next = expires;
@@ -1280,9 +1290,6 @@ remove_and_enqueue_same_base(struct hrtimer *timer, struct hrtimer_clock_base *btimerqueue_linked_del (&base->active, &timer->node); } -/* Set the new expiry time */ -hrtimer_set_expires_range_ns (timer, expires, delta_ns); -debug_activate (timer, mode, timer->is_queued); base->cpu_base->active_bases |=1 << base->index;
Signature · 1 line
2.53.0
The following commit has been merged into the timers/urgent branch of tip:
The following commit has been merged into the timers/urgent branch of tip:
Commit-ID: c5dcb3aadc18d7b82ba64790721b005d18193d35 Gitweb: https://git.kernel.org/tip/c5dcb3aadc18d7b82ba64790721b005d18193d35 Author: Andrea Parri <[email protected]> AuthorDate: Thu, 10 Sep 2026 16:34:42 +02:00 Committer: Thomas Gleixner <[email protected]> CommitterDate: Fri, 11 Sep 2026 10:15:42 +02:00
hrtimer: Use hard expiry when updating timers on the same base
Rearming a queued timer with nonzero slack can leave the timerqueue out of order. remove_and_enqueue_same_base() checks the new soft expiry against its neighbours' hard expiries, then stores the new hard expiry in the node without requeueing it.
For example, with A at 10 and B at 20, rearming A at 11 with slack 30 passes the neighbour check but leaves A's hard expiry of 41 before B's
20. The same function also caches the soft expiry in base->expires_next when updating or inserting the first timer, giving next-event selection an earlier deadline than the queue head's hard expiry.
Set the timer expiry before handling the queue. Use its stored hard expiry for the in-place ordering check and both updates to base->expires_next.
The early update is safe because remove_and_enqueue_same_base() runs with base->cpu_base->lock held. The lock keeps the queue stable while hrtimer_can_update_in_place() checks the new expiry against both neighbours. If the check fails, timerqueue_linked_del() removes the node without comparing expiry values before it is reinserted.
--- kernel/time/hrtimer.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-)
kernel/time/hrtimer.c +11 -4
@@ -1263,13 +1263,23 @@ remove_and_enqueue_same_base(struct hrtimer *timer, struct hrtimer_clock_base *b {bool was_first = false; +/* +* Updating the sort key while @timer is queued can temporarily +* make the tree inconsistent. This is safe under cpu_base->lock: +* no other queue operation can observe that state. +* hrtimer_can_update_in_place() either confirms that the new expiry +* fits between the neighbours or timerqueue_linked_del() removes the +* timer without consulting the expiry. +*/ +hrtimer_set_expires_range_ns (timer, expires, delta_ns); + expires =hrtimer_get_expires (timer); +/* Remove it from the timer queue if active */ if (timer->is_queued) { was_first = !timerqueue_linked_prev (&timer->node);/* Try to update in place to avoid the de/enqueue dance */ if (hrtimer_can_update_in_place (timer, base, expires)) { -hrtimer_set_expires_range_ns (timer, expires, delta_ns);trace_hrtimer_start (timer, mode, true);if (was_first) base->expires_next = expires;
@@ -1280,9 +1290,6 @@ remove_and_enqueue_same_base(struct hrtimer *timer, struct hrtimer_clock_base *btimerqueue_linked_del (&base->active, &timer->node); } -/* Set the new expiry time */ -hrtimer_set_expires_range_ns (timer, expires, delta_ns); -debug_activate (timer, mode, timer->is_queued); base->cpu_base->active_bases |=1 << base->index;