Re: [PATCH 0/4] Fix forward()/expires() racing with concurrent arming

"Gary Guo" <[email protected]>
Newsgroups org.kernel.vger.rust-for-linux
Message-ID <[email protected]>
On Thu Aug 13, 2026 at 2:48 PM BST, FUJITA Tomonori wrote:
> From: FUJITA Tomonori <[email protected]>
>
> This series started from the review of patches 3 and 4 [1]: a hrtimer
> can be armed from any CPU at any time, including while its callback
> runs, so restricting HrTimer::expires() to the callback context is not
> by itself enough to remove the race.
>
> It turned out that expires() is not the only problem. A callback may
> also change its expiry time with hrtimer_forward(), which is sound
> only because __run_hrtimer() dequeues the timer for the duration of
> the callback. Arming the same timer from another CPU puts it back into
> the rbtree while the callback runs, so hrtimer_forward() then changes
> the expiry of a timer that is queued, without the base lock and
> without re-checking the ordering, which leaves the tree unsorted.
>
> Two of the four pointer types cannot construct that
> situation. Starting a Pin<Box<T, A>> moves the box into the handle,
> and starting a Pin<&mut T> consumes the exclusive borrow, so in both
> cases nothing is left to arm the timer with. Arc<T> is Clone and
> Pin<&T> is Copy, and both of their start functions are reachable from
> safe code, so safe Rust could arm a timer whose callback was running.
>
> "No arming while the callback runs" cannot be expressed in the type
> system, because the callback begins when the timer expires rather than
> at any point in the Rust program, so patches 1 and 2 use the stronger
> "no arming while armed" instead. hrtimer_cancel() waits for the
> handler to return, which makes that the point where the right to arm
> can be handed back. The right to arm is split out of Arc<T> into
> HrTimerArc<T> and out of Pin<&T> into HrTimerPin<'a, T>, both
> non-clonable and consumed by start, modelled on ListArc; the object
> itself stays shareable through plain Arc references and shared pinned
> references respectively.
>
> Patches 3 and 4 are the previously posted expires() and
> repr(transparent) patches, unchanged. With patches 1 and 2 in place,
> the callback context has no concurrent writer of node.expires. So
> HrTimerCallbackContext::expires() is sound.

I am thinking about this and I wonder about a different approach: the only
reason that we're having this issue, is that `expires()` call and
`forward`/`forward_now` is executed outside the protection of the base lock.

The fix is easy -- to ensure that they are executed with the base lock held.
The callback wants either:
* Do not restart the timer
* Call hrtimer_forward[_now] and restart the timer

So, if we change the order from

    unlock base
    restart = fn(timer)
    lock base
    if restart {
        queue
    }

to

    get expires
    unlock base
    restart = fn(timer, expires)
    lock base
    match restart {
        Restart(now, interval) => {
            hrtimer_forward(timer, now, interval);
            queue
        }
        NoRestart => (),
    }

then we completely eradicate this issue.

Alternatively, we can add another spinlock to protect `expires` from race
condition from within callback and concurrent restart -- that is what perf core
does: perf_mux_hrtimer_handler and perf_mux_hrtimer_restart uses the same
hrtimer_lock to prevent race.

But further complicating the type system to prevent concurrent restart sounds
like a bad approach to me.

Best,
Gary

>
> [1]: https://lore.kernel.org/rust-for-linux/[email protected]/
>
> FUJITA Tomonori (4):
>   rust: hrtimer: Introduce HrTimerArc to make arming exclusive
>   rust: hrtimer: Introduce HrTimerPin to make arming exclusive
>   rust: hrtimer: Restrict expires() to safe contexts
>   rust: hrtimer: Make HrTimer repr(transparent)
>
>  rust/helpers/time.c                 |   6 ++
>  rust/kernel/time/hrtimer.rs         | 135 ++++++++++++++++------------
>  rust/kernel/time/hrtimer/arc.rs     | 113 +++++++++++++++++------
>  rust/kernel/time/hrtimer/pin.rs     | 107 +++++++++++++++-------
>  rust/kernel/time/hrtimer/pin_mut.rs |   2 +-
>  rust/kernel/time/hrtimer/tbox.rs    |   2 +-
>  6 files changed, 249 insertions(+), 116 deletions(-)
>
>
> base-commit: 643a7c306b8ce32743d4f94dd700c8588be37e66
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.