logo
Kurashizu Blogwhere ideas flow
  • Home
  • News
  • Blog
  • About
  • Home
  • News
  • Blog
  • About

© 2026 Kurashizu. All rights reserved.

Admin·New Post·Service Status
Powered by Cloudflare·31.10.26
← Back to news

Is There I/O After Death? What Happens to Io_uring When a Process Dies

Original article
blog.ydb.tech|28 points|3 comments|by porridgeraisin|Sep 2, 2026

👻 Is There I/O After Death?

Understanding io_uring Behavior During Process Termination

When a Linux process terminates—whether it exits gracefully or crashes violently—the kernel is tasked with a massive cleanup operation. While closing file descriptors and freeing memory is standard, io_uring introduces a complex layer of asynchronous operations. The central question is: What happens to the I/O requests that are still "in flight" when the process that requested them ceases to exist?


🏗️ The Architecture of io_uring

To understand the "afterlife" of I/O, we must first recall how io_uring functions. Unlike synchronous syscalls, io_uring utilizes two ring buffers shared between the kernel and user space:

  1. Submission Queue (SQ): Where the user writes requests.
  2. Completion Queue (CQ): Where the kernel writes results.

The Data Flow

Because these rings are mapped into the process's address space, a critical problem arises upon process death: The memory mappings vanish, but the kernel workers may still be processing the requests.


💀 The Moment of Death: The Cleanup Dilemma

In a traditional read() or write() call, the process is blocked. If the process dies, the syscall simply returns or the thread is terminated. However, io_uring is asynchronous\text{asynchronous}asynchronous.

The Core Conflict: The kernel cannot write a completion entry (CQE) to a memory region that no longer belongs to any living process. Attempting to do so would cause a kernel panic or memory corruption.

Comparison: Synchronous vs. Asynchronous Termination

FeatureSynchronous I/O (read/write)Asynchronous I/O (io_uring)
State at DeathProcess is usually blocked in syscallProcess may be doing other work or dead
CleanupStack is unwound, FD closedPending requests exist in kernel queues
Result DeliveryImmediate return to callerResult must be placed in CQ
RiskMinimalDangling pointers to user memory\text{Dangling pointers to user memory}Dangling pointers to user memory

🛠️ How the Kernel Manages the "Afterlife"

The kernel doesn't just "forget" about the io_uring instance. It uses a sophisticated reference-counting mechanism centered around the io_uring_ctx (the context object).

1. Reference Counting (kref\text{kref}kref)

The kernel maintains a reference count for the context.

  • The process holds one reference.
  • Every active asynchronous request (in-flight I/O) holds an additional reference.

Mathematically, the context is only destroyed when: Total References(RC)=0\text{Total References} (RC) = 0Total References(RC)=0

2. The Termination Sequence

When a process exits, the following sequence occurs:

  • The kernel triggers the release method of the io_uring file descriptor.
  • The process's primary reference to the io_uring_ctx is decremented.
  • The kernel immediately deletes the rings. →\rightarrow→ Incorrect. The kernel checks if RC>0RC > 0RC>0.
  • If requests are still pending, the context persists in a "zombie" state.

3. Handling the Results

Since the CQ is gone, the kernel cannot deliver the result to the user. Instead, it:

  1. Allows the I/O to complete (to avoid leaving hardware in an inconsistent state).
  2. Discards the completion result.
  3. Decrements the reference count for that specific request.

⚡ Special Cases and Edge Conditions

The IORING_SETUP_SQPOLL Thread

If the user enabled SQPOLL, a dedicated kernel thread is actively polling the SQ. This thread is not the process itself.

  • The Danger: The SQPOLL thread might try to read from the SQ just as the process dies.
  • The Fix: The kernel sends a signal to the SQPOLL thread to stop polling and exit before the memory is fully reclaimed.

Fixed Buffers and Files

io_uring allows "registering" buffers and files to avoid repeated mapping overhead.

// Conceptual registration
io_uring_register_buffers(&ring, iovecs, 1);

These are pinned in memory. The kernel must ensure that these pages are unpinned and the file references are dropped only after the final in-flight I/O has finished.


💻 Conceptual Implementation of Cleanup

If we were to represent the kernel's cleanup logic in pseudo-code, it would look like this:

def process_exit_handler(io_ctx):
    # Step 1: Stop the polling thread if it exists
    if io_ctx.sqpoll_thread:
        io_ctx.sqpoll_thread.stop()
    
    # Step 2: Drop the process's own reference
    io_ctx.ref_count -= 1
    
    # Step 3: Check if we can finally kill the context
    if io_ctx.ref_count == 0:
        finalize_io_uring_context(io_ctx)
    else:
        # The context stays alive until the last 
        # async worker calls 'put_ref()'
        log("Context persisting for pending I/O...")

🏁 Summary

In short, yes, there is I/O after death. The kernel ensures that io_uring operations are completed safely to maintain system stability, even if the originating process is long gone. By decoupling the lifetime of the request from the lifetime of the process via reference counting, Linux prevents crashes and memory leaks, ensuring that the "ghosts" of pending I/O are laid to rest properly.