Dev News Daily ENDE

Axboe proposes thread-identity swapping for io_uring

io_uring exists so that an application can submit work without blocking, and the kernel has always had an awkward problem underneath that promise: plenty of paths inside it were never written to be asynchronous. Operations in that category — the io_uring equivalents of fdatasync(), statx(), some openat() cases — are currently handed to a separate worker thread, so that the submitting thread can return from io_uring_enter() without waiting.

The cost of that arrangement is paid up front, whether or not it is needed. Waking a worker and context-switching into it is not free, and most of the time the operation would have completed immediately anyway. When it does block, the handoff was worth it; when it does not — the common case — the overhead is a large share of the request's total cost, which is precisely what the people reaching for io_uring were trying to avoid.

Jens Axboe's RFC, reported by LWN this week, inverts the order: run the operation on the submitting thread, and deal with the rare case where it really blocks.

Detecting that case needs the scheduler's help. A new task flag, PF_IO_HANDOFF, marks a thread as being in this mode; when such a thread is about to go to sleep, the scheduler calls into io_uring to say so. What happens next is the part that earns the article's "potentially scary": the two threads trade identities. The worker is made to look like the submitter — same thread ID, same signal-handling setup, and the rest — and carries on processing the submission ring before returning to user space. The thread that was about to block takes on the worker's identity and blocks as it would have anyway.

From user space, the thread that comes back out of io_uring_enter() looks like the thread that went in. It is a different task_struct.

Axboe proposes thread-identity swapping for io_uring
Axboe proposes thread-identity swapping for io_uring — Dev News Daily

What it means

This is a trade of one kind of complexity for another, and the RFC is honest about which kind. The alternative — making every blocking path in the kernel asynchronous — is the clean fix, and it has been chipped away at for years without being finished, because it touches everything. Identity swapping is confined to io_uring and the scheduler hook, which is a much smaller surface, at the price of a concept that is genuinely hard to reason about: "the thread you are talking to is not the thread you started with."

The place to watch is everything that observes thread identity from outside. Debuggers, tracing and profiling tools, anything that correlates by thread ID, per-thread resource accounting, and the assumptions of security tooling that treats a task_struct as stable. Those are exactly the tools people reach for when something in an io_uring application goes wrong, which makes this the kind of change whose cost shows up in incident response rather than in benchmarks.

And it is worth naming what this is optimising: a latency tax on the good case. That pattern is everywhere in systems work — a defensive step taken on every request because occasionally it will be needed. Whenever you have one of those, the question this patch set asks is worth asking: can you detect the bad case at the moment it happens, instead of paying for it in advance? Often you cannot without a hook in something you do not control, which is why Axboe needed the scheduler to cooperate.

It is an RFC, not a merged feature. Corbet's write-up at LWN is the place to follow what the kernel developers make of it.