bytecodealliance / bytecodealliance/rustix

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

Open
#1,603 0 comments 0 reactions 0 assignees View on GitHub
enhancement
Dominant language
Rust
Stars
2.1k
Forks
294
Avg merge
4h 7m
Merged PRs (30d)
2

Description

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

```rust
#[inline]
pub fn wake(uaddr: &AtomicU32, flags: Flags, val: u32) -> io::Result {
// 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:

```rust
struct Node {
state: AtomicU32,
next: AtomicPtr,
}

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:

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

or perhaps

```rust
pub unsafe fn wake_ptr(
uaddr: NonNull,
flags: Flags,
val: u32,
) -> io::Result;
```

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

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.