Skip to content

Expose raw-pointer futex wake/wait APIs instead of only &AtomicU32 #1603

@SchrodingerZhu

Description

@SchrodingerZhu

rustix::thread::futex::wake currently requires a Rust reference:

#[inline]
pub fn wake(uaddr: &AtomicU32, flags: Flags, val: u32) -> io::Result<usize> {
    // SAFETY: The raw pointers come from references or null.
    unsafe { futex_val2(uaddr, Operation::Wake, flags, val, 0, ptr::null(), 0) }
}

This is too restrictive for some valid futex use cases.

The kernel futex interface fundamentally operates on an address. In some synchronization designs, the caller only has a raw pointer to the futex word, and creating an &AtomicU32 is not justified by Rust’s reference rules.

A concrete example is a futex embedded in a node or frame owned by another thread, including stack allocation. The waking thread may only know the target futex address via a raw pointer stored in a queue. Once the waiter is woken, it may return immediately and destroy the node / stack frame before the waking function returns. In that situation, requiring &AtomicU32 forces the caller to materialize a reference where only a raw-pointer contract is actually available.

Sketch:

struct Node {
    state: AtomicU32,
    next: AtomicPtr<Node>,
}

fn waiter_thread() {
    let node = Node {
        state: AtomicU32::new(0),
        next: AtomicPtr::new(core::ptr::null_mut()),
    };

    enqueue(&node as *const Node);

    unsafe {
        // blocks while state == 0
        futex_wait_raw(core::ptr::addr_of!(node.state), 0);
    }

    // after wake, this stack frame may exit immediately
}

fn waking_thread(node: *const Node) {
    let futex_ptr = unsafe { core::ptr::addr_of!((*node).state) };

    unsafe {
        // This is the shape we need.
        // Constructing `&AtomicU32` here is the problem.
        futex_wake_raw(futex_ptr, Flags::PRIVATE, 1);
    }
}

Today, libraries built on top of rustix end up needing to do one of these:

  1. Manufacture &AtomicU32 from a raw pointer even though they only truly have an address-based contract.
  2. Avoid rustix for this case.
  3. Carry extra ownership/borrowing structure that the kernel API does not require.

The problem is not that a raw-pointer API would be “safe”; it should be unsafe. The point is that the unsafety boundary should match the actual contract: “caller must provide a valid futex address for the duration of the syscall”, not “caller must already have a Rust reference”.

Proposed addition:

pub unsafe fn wake_ptr(
    uaddr: *const AtomicU32,
    flags: Flags,
    val: u32,
) -> io::Result<usize>;

or perhaps

pub unsafe fn wake_ptr(
    uaddr: NonNull<AtomicU32>,
    flags: Flags,
    val: u32,
) -> io::Result<usize>;

That would let higher-level synchronization code express the correct contract without fabricating references where only raw-pointer validity is available.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions