initial macos porting work

This commit is contained in:
Will Temple
2026-03-23 15:16:20 -04:00
parent 4193457fc4
commit 861bf63621
40 changed files with 4575 additions and 233 deletions

View File

@@ -0,0 +1,340 @@
//! Public runtime driver primitives for macOS.
use std::cell::Cell;
use std::io;
use std::os::fd::RawFd;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;
#[derive(Clone)]
struct NotifierInner {
write_fd: RawFd,
closed: Arc<AtomicBool>,
}
impl NotifierInner {
fn notify(&self) -> io::Result<()> {
if self.closed.load(Ordering::Acquire) {
return Err(io::Error::new(
io::ErrorKind::BrokenPipe,
"target runtime driver is closed",
));
}
let byte = 1u8;
let written = unsafe {
libc::write(
self.write_fd,
&byte as *const u8 as *const libc::c_void,
std::mem::size_of::<u8>(),
)
};
if written < 0 {
let error = io::Error::last_os_error();
if error.kind() == io::ErrorKind::WouldBlock {
return Ok(());
}
return Err(error);
}
Ok(())
}
}
#[derive(Clone)]
/// Cross-thread notifier for a runtime thread's driver.
pub struct ThreadNotifier {
inner: NotifierInner,
}
impl ThreadNotifier {
/// Sends a wake notification to the target runtime thread.
pub fn notify(&self) -> io::Result<()> {
self.inner.notify()
}
}
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
/// Readiness information returned by [`Driver::poll`].
pub struct ReadyEvents {
/// One or more timer expirations are pending.
pub timer: bool,
/// One or more cross-thread wake notifications are pending.
pub wake: bool,
}
/// Low-level macOS runtime driver backed by `kqueue` and a wake pipe.
pub struct Driver {
kqueue_fd: RawFd,
wake_read_fd: RawFd,
wake_write_fd: RawFd,
closed: Arc<AtomicBool>,
timer_deadline: Cell<Option<Duration>>,
pending_wakes: Cell<u64>,
pending_timers: Cell<u64>,
}
/// Creates a new driver and its paired [`ThreadNotifier`].
pub fn create() -> io::Result<(Driver, ThreadNotifier)> {
create_driver()
}
/// Creates a new driver and its paired [`ThreadNotifier`].
pub fn create_driver() -> io::Result<(Driver, ThreadNotifier)> {
let kqueue_fd = cvt(unsafe { libc::kqueue() })?;
let mut pipe_fds = [0; 2];
cvt(unsafe { libc::pipe(pipe_fds.as_mut_ptr()) })?;
let wake_read_fd = pipe_fds[0];
let wake_write_fd = pipe_fds[1];
set_nonblocking(wake_read_fd)?;
set_nonblocking(wake_write_fd)?;
let event = libc::kevent {
ident: wake_read_fd as usize,
filter: libc::EVFILT_READ,
flags: libc::EV_ADD | libc::EV_ENABLE,
fflags: 0,
data: 0,
udata: std::ptr::null_mut(),
};
let submitted = unsafe {
libc::kevent(
kqueue_fd,
&event,
1,
std::ptr::null_mut(),
0,
std::ptr::null(),
)
};
if submitted < 0 {
let error = io::Error::last_os_error();
unsafe {
libc::close(wake_read_fd);
libc::close(wake_write_fd);
libc::close(kqueue_fd);
}
return Err(error);
}
let closed = Arc::new(AtomicBool::new(false));
let driver = Driver {
kqueue_fd,
wake_read_fd,
wake_write_fd,
closed: Arc::clone(&closed),
timer_deadline: Cell::new(None),
pending_wakes: Cell::new(0),
pending_timers: Cell::new(0),
};
let notifier = ThreadNotifier {
inner: NotifierInner {
write_fd: wake_write_fd,
closed,
},
};
Ok((driver, notifier))
}
impl Driver {
pub(crate) fn bind_current_thread(&self) {}
pub(crate) fn unbind_current_thread(&self) {}
/// Polls the driver without blocking.
pub fn poll(&self) -> io::Result<Option<ReadyEvents>> {
let mut pending = ReadyEvents::default();
if self.pending_wakes.get() > 0 {
pending.wake = true;
}
if self.pending_timers.get() > 0 {
pending.timer = true;
}
if pending.wake || pending.timer {
return Ok(Some(pending));
}
self.process(Some(Duration::ZERO))
}
/// Blocks until at least one event is available.
pub fn wait(&self) -> io::Result<()> {
let now = monotonic_now()?;
let timeout = self
.timer_deadline
.get()
.map(|deadline| deadline.saturating_sub(now));
let _ = self.process(timeout)?;
Ok(())
}
/// Updates the currently armed timer deadline.
pub fn rearm_timer(&self, deadline: Option<Duration>) -> io::Result<()> {
self.timer_deadline.set(deadline);
Ok(())
}
/// Drains the accumulated wake notification count.
pub fn drain_wake(&self) -> io::Result<u64> {
let wakes = self.pending_wakes.replace(0);
if wakes == 0 {
Err(io::Error::new(
io::ErrorKind::WouldBlock,
"no wake events are pending",
))
} else {
Ok(wakes)
}
}
/// Drains the accumulated timer-expiration count.
pub fn drain_timer(&self) -> io::Result<u64> {
let timers = self.pending_timers.replace(0);
if timers == 0 {
Err(io::Error::new(
io::ErrorKind::WouldBlock,
"no timer events are pending",
))
} else {
Ok(timers)
}
}
fn process(&self, timeout: Option<Duration>) -> io::Result<Option<ReadyEvents>> {
let mut ready = ReadyEvents::default();
let mut events = [unsafe { std::mem::zeroed::<libc::kevent>() }; 16];
let timeout_spec = timeout_to_timespec(timeout);
let timeout_ptr = timeout_spec
.as_ref()
.map_or(std::ptr::null(), |value| value as *const libc::timespec);
let result = unsafe {
libc::kevent(
self.kqueue_fd,
std::ptr::null(),
0,
events.as_mut_ptr(),
events.len() as i32,
timeout_ptr,
)
};
if result < 0 {
let error = io::Error::last_os_error();
if error.kind() != io::ErrorKind::Interrupted {
return Err(error);
}
}
let mut saw_any = false;
let count = result.max(0) as usize;
if count > 0 {
saw_any = true;
for event in events.iter().take(count) {
if event.ident as RawFd == self.wake_read_fd {
ready.wake = true;
let wakes = drain_wake_pipe(self.wake_read_fd)?;
self.pending_wakes
.set(self.pending_wakes.get().saturating_add(wakes));
}
}
}
if let Some(deadline) = self.timer_deadline.get()
&& monotonic_now()? >= deadline
{
ready.timer = true;
saw_any = true;
self.timer_deadline.set(None);
self.pending_timers
.set(self.pending_timers.get().saturating_add(1));
}
if saw_any { Ok(Some(ready)) } else { Ok(None) }
}
}
impl Drop for Driver {
fn drop(&mut self) {
self.closed.store(true, Ordering::Release);
unsafe {
libc::close(self.wake_read_fd);
libc::close(self.wake_write_fd);
libc::close(self.kqueue_fd);
}
}
}
/// Returns the current monotonic clock reading.
pub fn monotonic_now() -> io::Result<Duration> {
let mut now = std::mem::MaybeUninit::<libc::timespec>::uninit();
let result = unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC, now.as_mut_ptr()) };
if result < 0 {
return Err(io::Error::last_os_error());
}
let now = unsafe { now.assume_init() };
Ok(Duration::new(now.tv_sec as u64, now.tv_nsec as u32))
}
fn cvt(value: libc::c_int) -> io::Result<libc::c_int> {
if value < 0 {
Err(io::Error::last_os_error())
} else {
Ok(value)
}
}
fn set_nonblocking(fd: RawFd) -> io::Result<()> {
let flags = cvt(unsafe { libc::fcntl(fd, libc::F_GETFL) })?;
cvt(unsafe { libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK) })?;
Ok(())
}
fn timeout_to_timespec(timeout: Option<Duration>) -> Option<libc::timespec> {
timeout.map(|value| libc::timespec {
tv_sec: value.as_secs() as libc::time_t,
tv_nsec: value.subsec_nanos() as libc::c_long,
})
}
fn drain_wake_pipe(fd: RawFd) -> io::Result<u64> {
let mut wakes = 0u64;
let mut buf = [0u8; 256];
loop {
let read = unsafe {
libc::read(
fd,
buf.as_mut_ptr() as *mut libc::c_void,
buf.len() as libc::size_t,
)
};
if read > 0 {
wakes = wakes.saturating_add(read as u64);
continue;
}
if read == 0 {
break;
}
let error = io::Error::last_os_error();
if error.kind() == io::ErrorKind::WouldBlock {
break;
}
if error.kind() == io::ErrorKind::Interrupted {
continue;
}
return Err(error);
}
Ok(wakes.max(1))
}