Re: [REGRESSION] io_uring/futex: scalar wait/wake slowdown after 079afb081c42
From: Jens Axboe
Date: Thu Jul 30 2026 - 12:18:46 EST
On 7/30/26 8:34 AM, Jens Axboe wrote:
> On 7/30/26 4:42 AM, Chengfeng Lin wrote:
>> Hi Jens,
>>
>> I tested 079afb081c42 against its direct parent on bare metal. In a narrow
>> scalar io_uring futex wait/wake workload, the child was 9.27% slower. A
>> separate 338-line standalone reproduced the result at 10.02%. All compared
>> kernels actually ran with preempt=full.
>>
>> #regzbot introduced: 079afb081c4288e94d5e4223d3eb6306d853c68b
>> #regzbot title: io_uring scalar futex wait/wake slowdown
>>
>> This is a focused synthetic microbenchmark, not an application benchmark. It
>> uses one raw-UAPI ring and 32 cacheline-separated private futex words on one
>> pinned P-core. Each timed cycle submits 32 scalar IORING_OP_FUTEX_WAIT
>> requests, then 32 scalar IORING_OP_FUTEX_WAKE requests, and drains exactly 64
>> CQEs. Every wait must return 0 and every wake must return 1.
>>
>> I used a fresh boot for each point:
>>
>> 6a8118a77eec parent A -> 079afb081c42 child -> 6a8118a77eec parent B
>>
>> Each point had 3 warm-up rounds and 15 measured rounds. Every measured round
>> ran 512 cycles, or 16,384 wait/wake pairs. The results in ns/pair were:
>>
>> implementation parent A child parent B child vs midpoint
>> formal 180.079 196.647 179.856 +9.268%
>> standalone 180.171 197.856 179.502 +10.020%
>>
>> For the formal source, dropping the first measured round gave +9.269%. Parent
>> drift was -0.124%, and the maximum CV was 0.169%. The standalone drop-first
>> result was +9.996%, with -0.371% parent drift. All 90 scalar timing rows
>> passed the CQE, result, timeout, overflow, outstanding-request, and CPU
>> checks.
>>
>> An untimed child trace also hit io_futex_prep(), io_futex_wait(),
>> io_futex_wake(), and io_futex_complete() with the expected request counts.
>>
>> A matched WAITV -> WAKE profile changed by only +1.385%, below my preregistered
>> 5% signal gate, so my claim is limited to scalar wait/wake.
>>
>> I understand that 079afb fixes the exit-time use-after-free by keeping pending
>> private futex waits visible to cancellation before their mm state disappears.
>> Scalar WAIT and WAKE both use io_futex_prep(), so in the child both sides of
>> each measured pair execute the added tracking call. I am not suggesting a
>> revert.
>>
>> Is this per-request cost an expected trade-off for the lifetime fix, or could
>> the same exit/mm-lifetime guarantee be retained with cheaper tracking?
>>
>> Evidence bundle:
>>
>> https://github.com/lcf0399/linux-regression-evidence/tree/65c8cbf86f40cbe759e3f7db1d29d152ba03a8f2/io-uring-futex-inflight-wait-wake
>>
>> Standalone reproducer:
>>
>> https://github.com/lcf0399/linux-regression-evidence/tree/65c8cbf86f40cbe759e3f7db1d29d152ba03a8f2/io-uring-futex-inflight-wait-wake/reproducer
>
> Great report, thanks for that! I'll take a look at this. The inflight
> tracking is a bit of a big hammer for sure for this, and it isn't even
> needed on the wake side. Can you tell me what parameters you're using
> for the reproducers?
Try with these two patches.
--
Jens Axboe
From b47f14f3025f908dc71932a2ae4b82ffcad2cd37 Mon Sep 17 00:00:00 2001
From: Jens Axboe <axboe@xxxxxxxxx>
Date: Thu, 30 Jul 2026 06:50:47 -0600
Subject: [PATCH 2/2] io_uring/futex: only mark private futex waits as inflight
Inflight tracking of futex wait requests exists to ensure that do_exit()
-> io_uring_files_cancel() cancels them before the mm goes away, as a
private futex wait depends on the mm private futex hash staying alive
for the duration of the request.
Shared futexes have no such dependency. A FLAGS_SHARED request always
resolves to either an inode based key or an mm-shared key, both of which
fail futex_key_is_private() and hence always hash into the global futex
hash, whose lifetime isn't tied to the mm.
Only mark vectored futex waits as inflight if the futex is private.
Cc: stable@xxxxxxxxxxxxxxx
Fixes: 079afb081c42 ("io_uring/futex: mark wait requests as inflight")
Link: https://lore.kernel.org/io-uring/CANGjgdn=R_qyUdE=j9za+vkmqcxacbP-84OHXF4nZ4ho9qRyVg@xxxxxxxxxxxxxx/
Signed-off-by: Jens Axboe <axboe@xxxxxxxxx>
---
io_uring/futex.c | 17 +++++++++++++----
1 file changed, 13 insertions(+), 4 deletions(-)
diff --git a/io_uring/futex.c b/io_uring/futex.c
index eea0425f2bcb..f0d80a444f45 100644
--- a/io_uring/futex.c
+++ b/io_uring/futex.c
@@ -154,14 +154,16 @@ int io_futex_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe)
int io_futex_wait_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe)
{
+ struct io_futex *iof = io_kiocb_to_cmd(req, struct io_futex);
int ret;
ret = io_futex_prep(req, sqe);
if (unlikely(ret))
return ret;
- /* Mark as inflight, so file exit cancelation will find it */
- io_req_track_inflight(req);
+ /* inflight tracking only needed for mm private hash */
+ if (!(iof->futex_flags & FLAGS_SHARED))
+ io_req_track_inflight(req);
return 0;
}
@@ -186,6 +188,7 @@ int io_futexv_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe)
{
struct io_futex *iof = io_kiocb_to_cmd(req, struct io_futex);
struct io_futexv_data *ifd;
+ unsigned int i;
int ret;
/* No flags or mask supported for waitv */
@@ -210,8 +213,14 @@ int io_futexv_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe)
return ret;
}
- /* Mark as inflight, so file exit cancelation will find it */
- io_req_track_inflight(req);
+ /* inflight tracking only needed for mm private hash */
+ for (i = 0; i < iof->futex_nr; i++) {
+ if (!(ifd->futexv[i].w.flags & FLAGS_SHARED)) {
+ io_req_track_inflight(req);
+ break;
+ }
+ }
+
iof->futexv_unqueued = 0;
req->flags |= REQ_F_ASYNC_DATA;
req->async_data = ifd;
--
2.53.0
From abd086a38ea8442d87bbedc0ed944fecba964be8 Mon Sep 17 00:00:00 2001
From: Jens Axboe <axboe@xxxxxxxxx>
Date: Thu, 30 Jul 2026 06:50:13 -0600
Subject: [PATCH 1/2] io_uring/futex: don't mark futex wake requests as
inflight
Commit 079afb081c42 ("io_uring/futex: mark wait requests as inflight")
added inflight tracking to ensure that do_exit() ->
io_uring_files_cancel() finds and cancels pending futex waits before the
mm goes away, as a private futex wait depends on the mm private futex
hash staying alive for the duration of the request. However, as
io_futex_prep() is shared between FUTEX_WAIT and FUTEX_WAKE, wake
requests got marked as inflight as well.
A futex wake executes fully inline at issue time and never depends on
the mm staying alive after completion, hence there's no need to track
it. Kill it.
Cc: stable@xxxxxxxxxxxxxxx
Fixes: 079afb081c42 ("io_uring/futex: mark wait requests as inflight")
Reported-by: Chengfeng Lin <lin2530632123@xxxxxxxxx>
Link: https://lore.kernel.org/io-uring/CANGjgdn=R_qyUdE=j9za+vkmqcxacbP-84OHXF4nZ4ho9qRyVg@xxxxxxxxxxxxxx/
Signed-off-by: Jens Axboe <axboe@xxxxxxxxx>
---
io_uring/futex.c | 11 +++++++++++
io_uring/futex.h | 1 +
io_uring/opdef.c | 2 +-
3 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/io_uring/futex.c b/io_uring/futex.c
index 906701b3c5c6..eea0425f2bcb 100644
--- a/io_uring/futex.c
+++ b/io_uring/futex.c
@@ -149,6 +149,17 @@ int io_futex_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe)
!futex_validate_input(iof->futex_flags, iof->futex_mask))
return -EINVAL;
+ return 0;
+}
+
+int io_futex_wait_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe)
+{
+ int ret;
+
+ ret = io_futex_prep(req, sqe);
+ if (unlikely(ret))
+ return ret;
+
/* Mark as inflight, so file exit cancelation will find it */
io_req_track_inflight(req);
return 0;
diff --git a/io_uring/futex.h b/io_uring/futex.h
index d789fcf715e3..987db3f2c6d9 100644
--- a/io_uring/futex.h
+++ b/io_uring/futex.h
@@ -3,6 +3,7 @@
#include "cancel.h"
int io_futex_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe);
+int io_futex_wait_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe);
int io_futexv_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe);
int io_futex_wait(struct io_kiocb *req, unsigned int issue_flags);
int io_futexv_wait(struct io_kiocb *req, unsigned int issue_flags);
diff --git a/io_uring/opdef.c b/io_uring/opdef.c
index 4e58eb1344ea..cf3aa2242cd7 100644
--- a/io_uring/opdef.c
+++ b/io_uring/opdef.c
@@ -467,7 +467,7 @@ const struct io_issue_def io_issue_defs[] = {
},
[IORING_OP_FUTEX_WAIT] = {
#if defined(CONFIG_FUTEX)
- .prep = io_futex_prep,
+ .prep = io_futex_wait_prep,
.issue = io_futex_wait,
#else
.prep = io_eopnotsupp_prep,
--
2.53.0