From: "Gary Guo" <gary@garyguo.net>
To: "Thomas Gleixner" <tglx@kernel.org>,
"Andreas Hindborg" <a.hindborg@kernel.org>,
"Anna-Maria Behnsen" <anna-maria@linutronix.de>,
"Frederic Weisbecker" <frederic@kernel.org>,
"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
"Benno Lossin" <lossin@kernel.org>,
"Alice Ryhl" <aliceryhl@google.com>,
"Trevor Gross" <tmgross@umich.edu>,
"Danilo Krummrich" <dakr@kernel.org>,
"Daniel Almeida" <daniel.almeida@collabora.com>,
"Tamir Duberstein" <tamird@kernel.org>,
"Alexandre Courbot" <acourbot@nvidia.com>,
"Onur Özkan" <work@onurozkan.dev>,
"Jani Nikula" <jani.nikula@linux.intel.com>,
"Joonas Lahtinen" <joonas.lahtinen@linux.intel.com>,
"Rodrigo Vivi" <rodrigo.vivi@intel.com>,
"Tvrtko Ursulin" <tursulin@ursulin.net>,
"David Airlie" <airlied@gmail.com>,
"Simona Vetter" <simona@ffwll.ch>,
"Lyude Paul" <lyude@redhat.com>,
"John Stultz" <jstultz@google.com>,
"Stephen Boyd" <sboyd@kernel.org>
Cc: "Miguel Ojeda" <ojeda@kernel.org>,
"Boqun Feng" <boqun@kernel.org>, "Gary Guo" <gary@garyguo.net>,
"FUJITA Tomonori" <fujita.tomonori@gmail.com>,
<linux-kernel@vger.kernel.org>, <rust-for-linux@vger.kernel.org>,
<intel-gfx@lists.freedesktop.org>,
<dri-devel@lists.freedesktop.org>
Subject: Re: [PATCH 1/6] hrtimer: add expiry injecting callback variant
Date: Wed, 30 Sep 2026 14:14:51 +0100 [thread overview]
Message-ID: <DLSOFOGGZC92.12KYHEALX1271@garyguo.net> (raw)
In-Reply-To: <877bk3ixp5.ffs@fw13>
On Tue Sep 29, 2026 at 9:56 PM BST, Thomas Gleixner wrote:
> On Tue, Aug 25 2026 at 14:16, Andreas Hindborg wrote:
>> A hrtimer callback may modify its own expiry with hrtimer_forward()
>> and read it with hrtimer_get_expires(). Both run after
>> __run_hrtimer() has dropped the cpu_base->lock, so they race with a
>> concurrent hrtimer_start_range_ns() on another CPU, which rewrites
>> node.expires under the base lock and requeues the timer:
>>
>> - The unlocked read-modify-write of node.expires in
>> hrtimer_forward() is a data race against the locked write in the
>> start path.
>>
>> - The is_queued check in hrtimer_forward() is racy with
>> a time-of-check-time-of-use bug as well: a concurrent
>> start can enqueue the timer between the check and the expiry
>> update, and forwarding an already queued timer changes the expiry
>> of a node inside the timerqueue without re-sorting, leaving the
>> tree unordered.
>>
>> Timer users which both forward in the callback and arm from other contexts
>> must provide their own serialization, e.g. perf's cpc->hrtimer_lock plus
>> hrtimer_active flag, see commit 4cfafd3082af ("sched,perf: Fix periodic
>> timers"). The requirement is subtle and not enforced; i915_pmu and taprio
>> currently get it wrong. For the Rust hrtimer abstraction it is a soundness
>> problem: safe code can arm a timer whose callback is running, so callback
>> context forward and expiry reads cannot be offered as safe API.
>>
>> Add an alternative callback variant that removes the race
>> structurally instead of requiring serialization. An expiry injecting
>> callback receives the expiry snapshotted under the base lock by
>> value and, to restart the timer, fills a struct hrtimer_forward_args
>> and returns HRTIMER_RESTART. __run_hrtimer() then applies the
>> forward and the enqueue with the base lock held. The callback never
>> accesses live timer state.
>>
>> If a concurrent start enqueued the timer while the callback ran, the
>> restart request is discarded and the start wins, matching the
>> existing "restart == HRTIMER_RESTART && !timer->is_queued" handling
>> for classic callbacks. The is_queued check is reliable here: while
>> base->running == timer, hrtimer_try_to_cancel() bails out before
>> remove_hrtimer() and the timer cannot switch bases, so only a
>> concurrent start can enqueue it, and the start path writes the
>> expiry and is_queued in the same critical section. Thus !is_queued
>> at requeue time guarantees the expiry still equals the snapshot
>> handed to the callback: the deferred hrtimer_forward() cannot hit
>> its concurrent start check, and an overrun count the callback
>> derived from the snapshot is consistent with the forward that is
>> applied.
>>
>> The new callback pointer shares storage with the classic one in an
>> anonymous union, discriminated by a new is_ext flag placed in
>> existing padding; sizeof(struct hrtimer) is unchanged and the
>> classic callback path is unaffected. hrtimer_update_function()
>> rejects timers with an expiry injecting callback.
>
> Aside of the horrible name (what is "ext"?) the whole mechanism is
> creating a false sense of "safety" as the same problem exists when the
> timer callback simply sets the new expiry time without invoking
> hrtimer_forward().
>
> Also why is Rust special and wants to delegate the external
> serialization requirement to the hrtimer core code and thereby adding a
> boatload of extra conditionals into the hotpath to distinguish between
> the two callback variants?
>
> Yes, you found an example for the downside of this design in the i915
> code. That's not surprising because i915 is generally known for ignoring
> documentation and making up their own rules just because they can. So
> I'm not accepting this as an argument at all.
>
> I agree with you that the lack of enforcement of that external
> serialization rule is suboptimal. External serialization is hard to
> validate/enforce in general though it's not impossible.
>
> But let's first take a step back and look at the larger picture.
>
> The obvious question is:
>
> Why is the base lock dropped when invoking the callback?
>
> The answer is simply that the callback might and in many cases will
> acquire a lock which is used in the reverse lock order for the purpose
> of external serialization.
>
> If you think about that then it's pretty obvious that you can introduce
> a safe variant of hrtimer_forward() which can be invoked from the
> callback:
>
> hrtimer_forward_safe_from_callback(timer)
> {
> base = lock_running_timer_base(timer);
> if (!hrtimer_is_queued(timer))
> hrtimer_forward(timer);
> unlock_timer_base(base);
> }
FWIW we did discuss about this option. Here's the list of all options that we
come up with:
1. Take base lock before calling forward and release it afterward (the one you
mentioned above). This one requires exposing the base lock (at least to Rust
abstraction).
2. Prevent timer operation from within the callback and have hrtimer core doing
it (Andreas's patch).
3. Force users to stop the timer before re-arming. This was dismissed because
stopping the timer will require waiting for the callback, and this is prone to
deadlock condition.
4. Add a lock to Rust side that all users must take to forward / start. We don't
want to add a new lock just for this, and taking base lock would be more
favourable.
We consider (2) the best option because it avoids several pitfalls with (1):
- It is impossible to forget to forward and return restart, or forward but
return norestart.
- The base lock is not unlocked before relocked, so the forward and restarting
is atomic. So it's impossible to have the condition where the forward occurs,
but before returning RESTART, the hrtimer is concurrently queued.
- Unlock/relock have an overhead (we didn't quantify how much impact this will
be, though).
I supposed another alternative is to add a mode in hrtimer core where the base
lock is not unlocked before calling the callback, and expose base lock APIs
(like option 1) so that the Rust hrtimer abstraction can unlock it before
calling the callbacks.
Best,
Gary
>
> That prevents the scenario you described in a completely safe way, no?
>
> Coming back to validation/enforcement of external serialization. That's
> a problem which has been solved in other places already. Look at the
> seqlock code for inspiration.
next prev parent reply other threads:[~2026-09-30 13:15 UTC|newest]
Thread overview: 16+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-25 12:16 [PATCH 0/6] hrtimer: add an " Andreas Hindborg
2026-08-25 12:16 ` [PATCH 1/6] hrtimer: add " Andreas Hindborg
2026-09-29 20:56 ` Thomas Gleixner
2026-09-30 13:14 ` Gary Guo [this message]
2026-09-30 21:43 ` Thomas Gleixner
2026-09-30 23:29 ` Gary Guo
2026-10-01 13:45 ` Thomas Gleixner
2026-08-25 12:16 ` [PATCH 2/6] drm/i915/pmu: use the expiry injecting hrtimer callback Andreas Hindborg
2026-08-25 12:16 ` [PATCH 3/6] rust: hrtimer: use the expiry injecting callback variant Andreas Hindborg
2026-08-25 12:16 ` [PATCH 4/6] rust: hrtimer: restrict expires() to exclusive access Andreas Hindborg
2026-08-25 12:16 ` [PATCH 5/6] rust: hrtimer: document deadlock when starting a timer in its handler Andreas Hindborg
2026-08-25 13:31 ` Gary Guo
2026-08-26 9:31 ` Andreas Hindborg
2026-08-25 12:16 ` [PATCH 6/6] rust: hrtimer: Make HrTimer repr(transparent) Andreas Hindborg
2026-08-25 13:33 ` Gary Guo
2026-08-26 9:30 ` Andreas Hindborg
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=DLSOFOGGZC92.12KYHEALX1271@garyguo.net \
--to=gary@garyguo.net \
--cc=a.hindborg@kernel.org \
--cc=acourbot@nvidia.com \
--cc=airlied@gmail.com \
--cc=aliceryhl@google.com \
--cc=anna-maria@linutronix.de \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun@kernel.org \
--cc=dakr@kernel.org \
--cc=daniel.almeida@collabora.com \
--cc=dri-devel@lists.freedesktop.org \
--cc=frederic@kernel.org \
--cc=fujita.tomonori@gmail.com \
--cc=intel-gfx@lists.freedesktop.org \
--cc=jani.nikula@linux.intel.com \
--cc=joonas.lahtinen@linux.intel.com \
--cc=jstultz@google.com \
--cc=linux-kernel@vger.kernel.org \
--cc=lossin@kernel.org \
--cc=lyude@redhat.com \
--cc=ojeda@kernel.org \
--cc=rodrigo.vivi@intel.com \
--cc=rust-for-linux@vger.kernel.org \
--cc=sboyd@kernel.org \
--cc=simona@ffwll.ch \
--cc=tamird@kernel.org \
--cc=tglx@kernel.org \
--cc=tmgross@umich.edu \
--cc=tursulin@ursulin.net \
--cc=work@onurozkan.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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®