util/rust: Add atomic memory synchronization support

Add AtomicMemorySentinel for cross-platform atomic
memory synchronization. This provides a
platform-agnostic API over Linux futex syscalls,
allowing shared memory synchronization via
memory-mapped file descriptors.

Includes Linux futex implementation and stub
implementations for Windows and other platforms.

Signed-off-by: Dorinda Bassey <dbassey@redhat.com>
This commit is contained in:
Dorinda Bassey 2026-02-02 16:10:32 +01:00
parent 464daaab90
commit 4bd5f7dd59
11 changed files with 191 additions and 0 deletions

View file

@ -0,0 +1,88 @@
// Copyright 2026 Red Hat, Inc.
// SPDX-License-Identifier: MIT
//! AtomicMemorySentinel - Cross-platform atomic memory synchronization primitive.
//!
//! Provides access to futex and WaitOnAddress-like APIs for atomic memory operations.
use std::mem::size_of;
use std::sync::atomic::{AtomicU32, Ordering};
use crate::sys::platform::atomic_memory_sentinel;
use crate::MappedRegion;
use crate::MemoryMapping;
use crate::MesaError;
use crate::MesaResult;
/// A sentinel that provides access to atomic memory.
/// Safe to share across threads.
pub struct AtomicMemorySentinel {
_memory_mapping: MemoryMapping,
atomic_ptr: *mut u32,
}
// SAFETY: AtomicMemorySentinel can be sent across threads.
unsafe impl Send for AtomicMemorySentinel {}
// SAFETY: AtomicMemorySentinel can be shared across threads.
unsafe impl Sync for AtomicMemorySentinel {}
impl AtomicMemorySentinel {
/// Create a new AtomicMemorySentinel from a memory mapping.
pub fn new(memory_mapping: MemoryMapping) -> MesaResult<Self> {
if memory_mapping.size() < size_of::<u32>() {
return Err(MesaError::WithContext(
"memory mapping too small for AtomicU32",
));
}
let atomic_ptr = memory_mapping.as_ptr() as *mut u32;
Ok(Self {
_memory_mapping: memory_mapping,
atomic_ptr,
})
}
/// Signal that the atomic memory has changed.
///
/// This will wake up waiters on this sentinel.
pub fn signal(&self) -> MesaResult<()> {
// SAFETY: self.atomic_ptr is valid for the lifetime of the MemoryMapping,
// properly aligned, and we have exclusive ownership via the MemoryMapping.
let atomic_val = unsafe { AtomicU32::from_ptr(self.atomic_ptr) };
atomic_memory_sentinel::wake_bitset(atomic_val, i32::MAX, 1);
Ok(())
}
/// Load the current value from atomic memory.
pub fn load(&self) -> u32 {
// SAFETY: self.atomic_ptr is valid for the lifetime of the MemoryMapping,
// properly aligned, and we have exclusive ownership via the MemoryMapping.
let atomic_val = unsafe { AtomicU32::from_ptr(self.atomic_ptr) };
atomic_val.load(Ordering::SeqCst)
}
/// Store a value to atomic memory.
pub fn store(&self, val: u32) {
// SAFETY: self.atomic_ptr is valid for the lifetime of the MemoryMapping,
// properly aligned, and we have exclusive ownership via the MemoryMapping.
let atomic_val = unsafe { AtomicU32::from_ptr(self.atomic_ptr) };
atomic_val.store(val, Ordering::SeqCst);
}
/// Wake all threads waiting on this sentinel.
pub fn wake_all(&self) {
// SAFETY: self.atomic_ptr is valid for the lifetime of the MemoryMapping,
// properly aligned, and we have exclusive ownership via the MemoryMapping.
let atomic_val = unsafe { AtomicU32::from_ptr(self.atomic_ptr) };
atomic_memory_sentinel::wake_all(atomic_val);
}
/// Blocks until the value changes from `val` or is woken up.
pub fn wait(&self, val: u32) {
// SAFETY: self.atomic_ptr is valid for the lifetime of the MemoryMapping,
// properly aligned, and we have exclusive ownership via the MemoryMapping.
let atomic_val = unsafe { AtomicU32::from_ptr(self.atomic_ptr) };
atomic_memory_sentinel::wait_bitset(atomic_val, val, 1);
}
}

View file

@ -87,6 +87,7 @@ pub enum DescriptorType {
Unknown,
Memory(u32, u32), // (size, handle_type)
WritePipe,
Event,
}
/// # Safety

View file

@ -1,6 +1,7 @@
// Copyright 2025 Google
// SPDX-License-Identifier: MIT
mod atomic_memory_sentinel;
mod bytestream;
mod defines;
mod descriptor;
@ -9,6 +10,7 @@ mod memory_mapping;
mod shm;
mod sys;
pub use atomic_memory_sentinel::AtomicMemorySentinel;
pub use bytestream::Reader;
pub use bytestream::Writer;
pub use defines::*;

View file

@ -0,0 +1,40 @@
// Copyright 2026 Red Hat, Inc.
// SPDX-License-Identifier: MIT
//! Linux futex wrappers for cross-domain synchronization.
use std::num::NonZeroU32;
use std::sync::atomic::AtomicU32;
use rustix::thread::futex;
/// Wait on a futex with a bitset mask.
///
/// Blocks until the futex is woken up or the value changes from `val`.
pub fn wait_bitset(atomic_val: &AtomicU32, val: u32, bitset: u32) {
let flags = futex::Flags::PRIVATE;
let timeout = None;
let val3 = NonZeroU32::new(bitset).unwrap_or(NonZeroU32::new(1).unwrap());
// Ignore errors - futex::wait_bitset returns an error if the value
// has already changed or if interrupted, both are expected
let _ = futex::wait_bitset(atomic_val, flags, val, timeout, val3);
}
/// Wake threads waiting on a futex with a bitset mask.
///
/// Only wakes threads whose bitset matches the provided mask.
pub fn wake_bitset(atomic_val: &AtomicU32, val: i32, bitset: u32) {
let flags = futex::Flags::PRIVATE;
let val_u32 = if val < 0 { i32::MAX as u32 } else { val as u32 };
let val3 = NonZeroU32::new(bitset).unwrap_or(NonZeroU32::new(1).unwrap());
let _ = futex::wake_bitset(atomic_val, flags, val_u32, val3);
}
/// Wake all threads waiting on a futex.
pub fn wake_all(atomic_val: &AtomicU32) {
let flags = futex::Flags::PRIVATE;
// u32::MAX means wake all waiters
let _ = futex::wake(atomic_val, flags, u32::MAX);
}

View file

@ -41,6 +41,14 @@ impl OwnedDescriptor {
}
pub fn determine_type(&self) -> Result<DescriptorType> {
// Check for eventfd first (not seekable, special symlink)
if let Ok(fd_path) = read_link(format!("/proc/self/fd/{}", self.as_raw_descriptor())) {
let path_str = fd_path.to_string_lossy();
if path_str.starts_with("anon_inode:[eventfd]") {
return Ok(DescriptorType::Event);
}
}
match seek(&self.owned, SeekFrom::End(0)) {
Ok(seek_size) => {
let size: u32 = seek_size

View file

@ -1,6 +1,7 @@
// Copyright 2025 Google
// SPDX-License-Identifier: MIT
pub mod atomic_memory_sentinel;
pub mod descriptor;
pub mod event;
pub mod memory_mapping;

View file

@ -0,0 +1,24 @@
// Copyright 2026 Red Hat, Inc.
// SPDX-License-Identifier: MIT
//! Stub atomic memory synchronization implementation.
//!
//! The Linux backend uses futexes for atomic memory synchronization.
//! This stub implementation allows compilation on platforms without native support.
use std::sync::atomic::AtomicU32;
/// Stub implementation - no-op on non-Linux platforms.
pub fn wait_bitset(_atomic_val: &AtomicU32, _val: u32, _bitset: u32) {
todo!("atomic memory synchronization not implemented on this platform")
}
/// Stub implementation - no-op on non-Linux platforms.
pub fn wake_bitset(_atomic_val: &AtomicU32, _val: i32, _bitset: u32) {
todo!("atomic memory synchronization not implemented on this platform")
}
/// Stub implementation - no-op on non-Linux platforms.
pub fn wake_all(_atomic_val: &AtomicU32) {
todo!("atomic memory synchronization not implemented on this platform")
}

View file

@ -1,6 +1,7 @@
// Copyright 2025 Google
// SPDX-License-Identifier: MIT
pub mod atomic_memory_sentinel;
pub mod descriptor;
pub mod event;
pub mod memory_mapping;

View file

@ -0,0 +1,24 @@
// Copyright 2026 Red Hat, Inc.
// SPDX-License-Identifier: MIT
//! Stub atomic memory synchronization implementation for Windows.
//!
//! The Linux backend uses futexes for atomic memory synchronization.
//! This stub implementation allows compilation on Windows without native support.
use std::sync::atomic::AtomicU32;
/// Stub implementation - no-op on Windows.
pub fn wait_bitset(_atomic_val: &AtomicU32, _val: u32, _bitset: u32) {
todo!("atomic memory synchronization not implemented on this platform")
}
/// Stub implementation - no-op on Windows.
pub fn wake_bitset(_atomic_val: &AtomicU32, _val: i32, _bitset: u32) {
todo!("atomic memory synchronization not implemented on this platform")
}
/// Stub implementation - no-op on Windows.
pub fn wake_all(_atomic_val: &AtomicU32) {
todo!("atomic memory synchronization not implemented on this platform")
}

View file

@ -1,6 +1,7 @@
// Copyright 2025 Google
// SPDX-License-Identifier: MIT
pub mod atomic_memory_sentinel;
pub mod descriptor;
pub mod event;
pub mod memory_mapping;

View file

@ -30,6 +30,7 @@ if host_machine.system() == 'linux' or host_machine.system() == 'android'
'--cfg', 'feature="net"',
'--cfg', 'feature="param"',
'--cfg', 'feature="pipe"',
'--cfg', 'feature="thread"',
]
elif host_machine.system() == 'darwin' or host_machine.system() == 'macos'
rustix_args += [