Re: Uclamp races due to non-atomic static_branch_likely updates

From: John Stultz

Date: Wed Oct 21 2026 - 13:56:32 EST


On Wed, Oct 21, 2026 at 6:34 AM Peter Zijlstra <peterz@xxxxxxxxxxxxx> wrote:
>
> On Wed, Oct 21, 2026 at 09:21:36AM -0400, Steven Rostedt wrote:
>
> > > And yeah, its always been the case that jump_labels (and static_branch)
> > > updates are non-atomic. Most of them are init time, and don't care.
> > >
> > > Now, I'm sure we've ran into this particular problem before, and it
> > > always requires care.
> > >
>
>
> Read this part:
>
> > > Also note that a simple global boolean has the exact same problem.
> > > Adding runtime conditionals isn't going to fix this.
>
> again, and then consider your response..
>
> >
> > Another idea for "atomic static branch" is to have two static branches at
> > each location. One that switches to a "check this variable and do a normal
> > test and branch" and "we are now in sync and can use the original static
> > branch".
> >
> > Something like:
> >
> > if (static_branch_unlikely(&foo_use_variable.key)) {
> > return static_key_enabled(&foo->key);
> > } else {
> > if (static_branch_unlikely(&foo->key))
> > return true;
> > else
> > return false;
> > }
> >
> > And then in between updates of foo, the foo_use_variable gets set making
> > all the branches use the key, which will be updated "atomically" (maybe
> > some memory barriers needs to be added). After the updates of all the
> > static keys are complete, the foo_use_variable gets unset.
> >
> > The non-atomic portion of "foo_use_variable" is OK because the foo->key
> > will match whatever the static_branch version it has.
>
> That simply doesn't work.
>
> Even *IF* memory stores would propagate instantly (they don't) you can
> still have the boolean swap while in the middle of this state.
>
> ---
>
> B = true;
>
> CPU0 CPU1
>
> enqueue
> if (B)
> something++;
>
> B = false;
>
> dequeue
> if (B)
> something--;
>
>
> ---
>
> Will still be left with something unbalanced, even if everything is
> super duper magically atomic.

Sure, but this sort of race with a flag variable is much more common
and easier to think about (particularly if the flag value is a one way
transition), since there is usually a causal relationship between
enqueue and dequeue.

But I'd argue the static_branch/jump_label updates being non-atomic
(and ordered by address) can make for much more subtle and unintuitive
results that seem non-causual:

B = true;
CPU0 CPU1
B = false;
init
if (B) //sees true
something=0

enqueue
if (B) //sees false
something++;

dequeue
if (B) //sees true
something--;

And yes, compilers and cpus can do a lot of reordering to also break
causality in similar ways, but there's no barrier-primitive here that
can be used to force the order of things.

Again, it may just be a natural consequence that the static key is
*not* actually just a flag and that's fine. But given it looks a lot
like one, it makes it a potentially shiny footgun. (I should know, as
I'm now looking at a strange hole in my foot after realizing my
out-of-tree patch to allow one-way enabling of proxy-exec at runtime
for testing is now "a bit problematic" :)

thanks
-john