Cleanup
This commit is contained in:
@@ -13,16 +13,12 @@ use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
|
||||
use std::time::Duration;
|
||||
|
||||
use super::driver::{Driver, ThreadNotifier, create_driver, monotonic_now};
|
||||
use crate::platform::runtime_shared::{
|
||||
IntervalCallback, LocalBoxFuture, LocalTask, LocalTaskQueue, MICROTASK_STARVATION_THRESHOLD,
|
||||
MacroTaskQueue, SendTask,
|
||||
};
|
||||
use crate::trace_targets;
|
||||
|
||||
type LocalTask = Box<dyn FnOnce() + 'static>;
|
||||
type SendTask = Box<dyn FnOnce() + Send + 'static>;
|
||||
type LocalBoxFuture = Pin<Box<dyn Future<Output = ()> + 'static>>;
|
||||
|
||||
/// If the microtask queue runs more than this many tasks in a single turn
|
||||
/// without yielding to the macrotask queue, a warning is emitted.
|
||||
const MICROTASK_STARVATION_THRESHOLD: u64 = 1000;
|
||||
|
||||
struct MacroTask {
|
||||
task: LocalTask,
|
||||
/// Wall time at which this task entered the local queue. Populated only
|
||||
@@ -668,12 +664,12 @@ struct ThreadState {
|
||||
driver: Driver,
|
||||
shared: Arc<ThreadShared>,
|
||||
worker_completion: Option<Arc<WorkerCompletion>>,
|
||||
local_microtasks: RefCell<VecDeque<LocalTask>>,
|
||||
local_macrotasks: RefCell<VecDeque<MacroTask>>,
|
||||
local_microtasks: RefCell<LocalTaskQueue>,
|
||||
local_macrotasks: RefCell<MacroTaskQueue<MacroTask>>,
|
||||
timers: RefCell<TimerHeap>,
|
||||
/// Zero-delay intervals bypasses the timer heap entirely. Each entry
|
||||
/// re-enqueues itself as a macrotask on every turn.
|
||||
immediate_intervals: RefCell<HashMap<usize, Rc<RefCell<Box<dyn FnMut()>>>>>,
|
||||
immediate_intervals: RefCell<HashMap<usize, IntervalCallback>>,
|
||||
next_timer_id: Cell<usize>,
|
||||
children: RefCell<Vec<ChildWorker>>,
|
||||
}
|
||||
|
||||
@@ -1,12 +1,37 @@
|
||||
//! Public runtime driver primitives for macOS.
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::collections::HashMap;
|
||||
use std::io;
|
||||
use std::os::fd::RawFd;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::op::completion::CompletionHandle;
|
||||
|
||||
type FdCompletion = CompletionHandle<io::Result<()>>;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
pub(crate) struct FdReadinessToken(u64);
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
pub(crate) enum FdInterest {
|
||||
Readable,
|
||||
Writable,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
struct FdKey {
|
||||
fd: RawFd,
|
||||
interest: FdInterest,
|
||||
}
|
||||
|
||||
struct FdWaiter {
|
||||
token: FdReadinessToken,
|
||||
completion: FdCompletion,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct NotifierInner {
|
||||
write_fd: RawFd,
|
||||
@@ -73,6 +98,8 @@ pub struct Driver {
|
||||
timer_deadline: Cell<Option<Duration>>,
|
||||
pending_wakes: Cell<u64>,
|
||||
pending_timers: Cell<u64>,
|
||||
next_fd_token: Cell<u64>,
|
||||
fd_waiters: RefCell<HashMap<FdKey, Vec<FdWaiter>>>,
|
||||
}
|
||||
|
||||
/// Creates a new driver and its paired [`ThreadNotifier`].
|
||||
@@ -130,6 +157,8 @@ pub fn create_driver() -> io::Result<(Driver, ThreadNotifier)> {
|
||||
timer_deadline: Cell::new(None),
|
||||
pending_wakes: Cell::new(0),
|
||||
pending_timers: Cell::new(0),
|
||||
next_fd_token: Cell::new(1),
|
||||
fd_waiters: RefCell::new(HashMap::new()),
|
||||
};
|
||||
|
||||
let notifier = ThreadNotifier {
|
||||
@@ -205,6 +234,51 @@ impl Driver {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn register_fd_readiness(
|
||||
&self,
|
||||
fd: RawFd,
|
||||
interest: FdInterest,
|
||||
completion: FdCompletion,
|
||||
) -> io::Result<FdReadinessToken> {
|
||||
let key = FdKey { fd, interest };
|
||||
let should_register = !self.fd_waiters.borrow().contains_key(&key);
|
||||
if should_register {
|
||||
self.update_fd_interest(key, libc::EV_ADD | libc::EV_ENABLE)?;
|
||||
}
|
||||
|
||||
let token = self.allocate_fd_token();
|
||||
self.fd_waiters
|
||||
.borrow_mut()
|
||||
.entry(key)
|
||||
.or_default()
|
||||
.push(FdWaiter { token, completion });
|
||||
Ok(token)
|
||||
}
|
||||
|
||||
pub(crate) fn cancel_fd_readiness(&self, token: FdReadinessToken) {
|
||||
let mut empty_key = None;
|
||||
{
|
||||
let mut waiters = self.fd_waiters.borrow_mut();
|
||||
for (key, entries) in waiters.iter_mut() {
|
||||
if let Some(index) = entries.iter().position(|entry| entry.token == token) {
|
||||
entries.swap_remove(index);
|
||||
if entries.is_empty() {
|
||||
empty_key = Some(*key);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(key) = empty_key {
|
||||
waiters.remove(&key);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(key) = empty_key {
|
||||
let _ = self.update_fd_interest(key, libc::EV_DELETE);
|
||||
}
|
||||
}
|
||||
|
||||
fn process(&self, timeout: Option<Duration>) -> io::Result<Option<ReadyEvents>> {
|
||||
let mut ready = ReadyEvents::default();
|
||||
|
||||
@@ -241,6 +315,8 @@ impl Driver {
|
||||
let wakes = drain_wake_pipe(self.wake_read_fd)?;
|
||||
self.pending_wakes
|
||||
.set(self.pending_wakes.get().saturating_add(wakes));
|
||||
} else if let Some(interest) = interest_from_filter(event.filter) {
|
||||
self.complete_fd_waiters(event.ident as RawFd, interest, event);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -257,6 +333,59 @@ impl Driver {
|
||||
|
||||
if saw_any { Ok(Some(ready)) } else { Ok(None) }
|
||||
}
|
||||
|
||||
fn allocate_fd_token(&self) -> FdReadinessToken {
|
||||
let token = self.next_fd_token.get();
|
||||
self.next_fd_token.set(
|
||||
token
|
||||
.checked_add(1)
|
||||
.expect("fd readiness token space exhausted"),
|
||||
);
|
||||
FdReadinessToken(token)
|
||||
}
|
||||
|
||||
fn update_fd_interest(&self, key: FdKey, flags: u16) -> io::Result<()> {
|
||||
let event = libc::kevent {
|
||||
ident: key.fd as usize,
|
||||
filter: filter_for_interest(key.interest),
|
||||
flags,
|
||||
fflags: 0,
|
||||
data: 0,
|
||||
udata: std::ptr::null_mut(),
|
||||
};
|
||||
let submitted = unsafe {
|
||||
libc::kevent(
|
||||
self.kqueue_fd,
|
||||
&event,
|
||||
1,
|
||||
std::ptr::null_mut(),
|
||||
0,
|
||||
std::ptr::null(),
|
||||
)
|
||||
};
|
||||
if submitted < 0 {
|
||||
Err(io::Error::last_os_error())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn complete_fd_waiters(&self, fd: RawFd, interest: FdInterest, event: &libc::kevent) {
|
||||
let key = FdKey { fd, interest };
|
||||
let waiters = self.fd_waiters.borrow_mut().remove(&key);
|
||||
let Some(waiters) = waiters else {
|
||||
return;
|
||||
};
|
||||
|
||||
let _ = self.update_fd_interest(key, libc::EV_DELETE);
|
||||
let result = fd_event_result(event);
|
||||
for waiter in waiters {
|
||||
waiter.completion.complete(match &result {
|
||||
Ok(()) => Ok(()),
|
||||
Err(error) => Err(io::Error::new(error.kind(), error.to_string())),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Driver {
|
||||
@@ -290,6 +419,31 @@ fn cvt(value: libc::c_int) -> io::Result<libc::c_int> {
|
||||
}
|
||||
}
|
||||
|
||||
fn filter_for_interest(interest: FdInterest) -> i16 {
|
||||
match interest {
|
||||
FdInterest::Readable => libc::EVFILT_READ,
|
||||
FdInterest::Writable => libc::EVFILT_WRITE,
|
||||
}
|
||||
}
|
||||
|
||||
fn interest_from_filter(filter: i16) -> Option<FdInterest> {
|
||||
if filter == libc::EVFILT_READ {
|
||||
Some(FdInterest::Readable)
|
||||
} else if filter == libc::EVFILT_WRITE {
|
||||
Some(FdInterest::Writable)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn fd_event_result(event: &libc::kevent) -> io::Result<()> {
|
||||
if event.flags & libc::EV_ERROR != 0 && event.data != 0 {
|
||||
Err(io::Error::from_raw_os_error(event.data as i32))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
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) })?;
|
||||
|
||||
@@ -12,18 +12,13 @@ use std::sync::{Arc, Mutex, MutexGuard};
|
||||
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
|
||||
use std::time::Duration;
|
||||
|
||||
use super::driver::{Driver, ThreadNotifier, create_driver, monotonic_now};
|
||||
use super::driver::{Driver, FdReadinessToken, ThreadNotifier, create_driver, monotonic_now};
|
||||
use crate::platform::runtime_shared::{
|
||||
IntervalCallback, LocalBoxFuture, LocalTask, LocalTaskQueue, MICROTASK_STARVATION_THRESHOLD,
|
||||
MacroTaskQueue, SendTask,
|
||||
};
|
||||
use crate::trace_targets;
|
||||
|
||||
type LocalTask = Box<dyn FnOnce() + 'static>;
|
||||
type SendTask = Box<dyn FnOnce() + Send + 'static>;
|
||||
type LocalBoxFuture = Pin<Box<dyn Future<Output = ()> + 'static>>;
|
||||
type IntervalCallback = Rc<RefCell<Box<dyn FnMut()>>>;
|
||||
|
||||
/// If the microtask queue runs more than this many tasks in a single turn
|
||||
/// without yielding to the macrotask queue, a warning is emitted.
|
||||
const MICROTASK_STARVATION_THRESHOLD: u64 = 1000;
|
||||
|
||||
struct MacroTask {
|
||||
task: LocalTask,
|
||||
/// Wall time at which this task entered the local queue. Populated only
|
||||
@@ -100,6 +95,10 @@ pub(crate) fn with_current_driver<T>(f: impl FnOnce(&Driver) -> T) -> T {
|
||||
f(¤t_thread().driver)
|
||||
}
|
||||
|
||||
pub(crate) fn cancel_fd_readiness(token: FdReadinessToken) {
|
||||
current_thread().driver.cancel_fd_readiness(token);
|
||||
}
|
||||
|
||||
/// Queues a macrotask on the current runtime thread.
|
||||
///
|
||||
/// The task runs after all currently-queued macrotasks, and after all microtasks.
|
||||
@@ -670,8 +669,8 @@ struct ThreadState {
|
||||
driver: Driver,
|
||||
shared: Arc<ThreadShared>,
|
||||
worker_completion: Option<Arc<WorkerCompletion>>,
|
||||
local_microtasks: RefCell<VecDeque<LocalTask>>,
|
||||
local_macrotasks: RefCell<VecDeque<MacroTask>>,
|
||||
local_microtasks: RefCell<LocalTaskQueue>,
|
||||
local_macrotasks: RefCell<MacroTaskQueue<MacroTask>>,
|
||||
timers: RefCell<TimerHeap>,
|
||||
/// Zero-delay intervals bypasses the timer heap entirely. Each entry
|
||||
/// re-enqueues itself as a macrotask on every turn.
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
pub(crate) mod runtime_shared;
|
||||
|
||||
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
|
||||
pub mod linux_x86_64;
|
||||
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
|
||||
|
||||
17
lib/runtime/src/platform/runtime_shared.rs
Normal file
17
lib/runtime/src/platform/runtime_shared.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
use std::cell::RefCell;
|
||||
use std::collections::VecDeque;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub(crate) type LocalTask = Box<dyn FnOnce() + 'static>;
|
||||
pub(crate) type SendTask = Box<dyn FnOnce() + Send + 'static>;
|
||||
pub(crate) type LocalBoxFuture = Pin<Box<dyn Future<Output = ()> + 'static>>;
|
||||
pub(crate) type IntervalCallback = Rc<RefCell<Box<dyn FnMut()>>>;
|
||||
|
||||
/// If the microtask queue runs more than this many tasks in a single turn
|
||||
/// without yielding to the macrotask queue, a warning is emitted.
|
||||
pub(crate) const MICROTASK_STARVATION_THRESHOLD: u64 = 1000;
|
||||
|
||||
pub(crate) type LocalTaskQueue = VecDeque<LocalTask>;
|
||||
pub(crate) type MacroTaskQueue<T> = VecDeque<T>;
|
||||
@@ -2,42 +2,48 @@
|
||||
|
||||
use std::io;
|
||||
use std::os::fd::RawFd;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::op::completion::completion_for_current_thread;
|
||||
use crate::platform::current::driver::FdInterest;
|
||||
use crate::platform::current::runtime::{
|
||||
cancel_fd_readiness, current_thread_handle, with_current_driver,
|
||||
};
|
||||
|
||||
/// Waits until `fd` becomes readable or reports an error/hangup condition.
|
||||
pub async fn wait_readable(fd: RawFd) -> io::Result<()> {
|
||||
let (future, handle) = completion_for_current_thread::<io::Result<()>>();
|
||||
std::thread::spawn(move || {
|
||||
let mut pollfd = libc::pollfd {
|
||||
fd,
|
||||
events: libc::POLLIN | libc::POLLERR | libc::POLLHUP,
|
||||
revents: 0,
|
||||
};
|
||||
loop {
|
||||
let result = unsafe { libc::poll(&mut pollfd, 1, -1) };
|
||||
if result > 0 {
|
||||
handle.complete(Ok(()));
|
||||
return;
|
||||
}
|
||||
if result == 0 {
|
||||
continue;
|
||||
}
|
||||
wait_fd_readiness(fd, FdInterest::Readable).await
|
||||
}
|
||||
|
||||
let error = io::Error::last_os_error();
|
||||
if error.kind() == io::ErrorKind::Interrupted {
|
||||
continue;
|
||||
}
|
||||
handle.complete(Err(error));
|
||||
return;
|
||||
/// Waits until `fd` becomes writable or reports an error/hangup condition.
|
||||
pub async fn wait_writable(fd: RawFd) -> io::Result<()> {
|
||||
wait_fd_readiness(fd, FdInterest::Writable).await
|
||||
}
|
||||
|
||||
async fn wait_fd_readiness(fd: RawFd, interest: FdInterest) -> io::Result<()> {
|
||||
let (future, handle) = completion_for_current_thread::<io::Result<()>>();
|
||||
let owner = current_thread_handle();
|
||||
let token =
|
||||
with_current_driver(|driver| driver.register_fd_readiness(fd, interest, handle.clone()));
|
||||
match token {
|
||||
Ok(token) => {
|
||||
handle.set_cancel({
|
||||
let handle = handle.clone();
|
||||
move || {
|
||||
let queued_handle = handle.clone();
|
||||
let queued = owner.queue_task(move || {
|
||||
cancel_fd_readiness(token);
|
||||
queued_handle.finish(None);
|
||||
});
|
||||
if !queued {
|
||||
handle.finish(None);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
Err(error) => {
|
||||
handle.complete(Err(error));
|
||||
}
|
||||
}
|
||||
|
||||
future.await
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn _duration_to_poll_timeout(duration: Duration) -> i32 {
|
||||
duration.as_millis().min(i32::MAX as u128) as i32
|
||||
}
|
||||
|
||||
@@ -8,8 +8,9 @@ use std::os::fd::{FromRawFd, OwnedFd};
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::os::unix::fs::{FileTypeExt, MetadataExt, OpenOptionsExt};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::OnceLock;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::{Arc, Mutex, mpsc};
|
||||
use std::task::{Context, Poll, Waker};
|
||||
use std::thread;
|
||||
|
||||
@@ -17,6 +18,10 @@ use crate::op::completion::completion_for_current_thread;
|
||||
use crate::op::fs::{FileType, FsOp, MetadataTarget, RawDirEntry, RawMetadata};
|
||||
use crate::platform::current::runtime::{ThreadHandle, current_thread_handle};
|
||||
|
||||
type BlockingTask = Box<dyn FnOnce() + Send + 'static>;
|
||||
|
||||
static BLOCKING_POOL: OnceLock<io::Result<BlockingPool>> = OnceLock::new();
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum ExecutionPath {
|
||||
Offload,
|
||||
@@ -227,12 +232,9 @@ impl ReadDirStream {
|
||||
let state = Arc::new(ReadDirState::new(current_thread_handle()));
|
||||
let producer = Arc::clone(&state);
|
||||
|
||||
if let Err(error) = thread::Builder::new()
|
||||
.name("ruin-runtime-read-dir".into())
|
||||
.spawn(move || produce_dir_entries(path, producer))
|
||||
{
|
||||
if let Err(error) = spawn_blocking(Box::new(move || produce_dir_entries(path, producer))) {
|
||||
state.release_pending();
|
||||
return Err(io::Error::other(error));
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
Ok(Self { state })
|
||||
@@ -357,12 +359,76 @@ async fn offload<T: Send + 'static>(
|
||||
work: impl FnOnce() -> io::Result<T> + Send + 'static,
|
||||
) -> io::Result<T> {
|
||||
let (future, handle) = completion_for_current_thread::<io::Result<T>>();
|
||||
thread::spawn(move || {
|
||||
handle.complete(work());
|
||||
});
|
||||
let handle_for_task = handle.clone();
|
||||
if let Err(error) = spawn_blocking(Box::new(move || handle_for_task.complete(work()))) {
|
||||
handle.complete(Err(error));
|
||||
}
|
||||
future.await
|
||||
}
|
||||
|
||||
struct BlockingPool {
|
||||
sender: mpsc::Sender<BlockingTask>,
|
||||
}
|
||||
|
||||
impl BlockingPool {
|
||||
fn spawn(&self, task: BlockingTask) -> io::Result<()> {
|
||||
self.sender.send(task).map_err(|_| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::BrokenPipe,
|
||||
"filesystem blocking worker pool has stopped",
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_blocking(task: BlockingTask) -> io::Result<()> {
|
||||
blocking_pool()?.spawn(task)
|
||||
}
|
||||
|
||||
fn blocking_pool() -> io::Result<&'static BlockingPool> {
|
||||
match BLOCKING_POOL.get_or_init(create_blocking_pool) {
|
||||
Ok(pool) => Ok(pool),
|
||||
Err(error) => Err(io::Error::new(error.kind(), error.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
fn create_blocking_pool() -> io::Result<BlockingPool> {
|
||||
let (sender, receiver) = mpsc::channel::<BlockingTask>();
|
||||
let receiver = Arc::new(Mutex::new(receiver));
|
||||
let worker_count = std::thread::available_parallelism()
|
||||
.map(usize::from)
|
||||
.unwrap_or(2)
|
||||
.clamp(2, 4);
|
||||
let mut spawned = 0usize;
|
||||
let mut last_error = None;
|
||||
|
||||
for index in 0..worker_count {
|
||||
let receiver = Arc::clone(&receiver);
|
||||
match thread::Builder::new()
|
||||
.name(format!("ruin-runtime-fs-offload-{index}"))
|
||||
.spawn(move || {
|
||||
loop {
|
||||
let task = receiver.lock().unwrap().recv();
|
||||
match task {
|
||||
Ok(task) => task(),
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
}) {
|
||||
Ok(_) => spawned += 1,
|
||||
Err(error) => last_error = Some(error),
|
||||
}
|
||||
}
|
||||
|
||||
if spawned == 0 {
|
||||
return Err(io::Error::other(
|
||||
last_error.expect("at least one worker spawn should have been attempted"),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(BlockingPool { sender })
|
||||
}
|
||||
|
||||
fn path_to_c_string(path: PathBuf) -> io::Result<CString> {
|
||||
CString::new(path.as_os_str().as_bytes()).map_err(|_| {
|
||||
io::Error::new(
|
||||
|
||||
@@ -23,11 +23,24 @@ type ShutdownFuture = Pin<Box<dyn Future<Output = io::Result<()>> + 'static>>;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum ExecutionPath {
|
||||
Kqueue,
|
||||
Offload,
|
||||
}
|
||||
|
||||
pub fn execution_path(_op: &NetOp) -> ExecutionPath {
|
||||
ExecutionPath::Offload
|
||||
pub fn execution_path(op: &NetOp) -> ExecutionPath {
|
||||
match op {
|
||||
NetOp::Socket { .. }
|
||||
| NetOp::Connect { .. }
|
||||
| NetOp::Bind { .. }
|
||||
| NetOp::Listen { .. }
|
||||
| NetOp::Accept { .. }
|
||||
| NetOp::Send { .. }
|
||||
| NetOp::SendTo { .. }
|
||||
| NetOp::Recv { .. }
|
||||
| NetOp::RecvFrom { .. }
|
||||
| NetOp::Shutdown { .. }
|
||||
| NetOp::Close { .. } => ExecutionPath::Kqueue,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn resolve_addrs<A>(addr: A) -> io::Result<Vec<SocketAddr>>
|
||||
@@ -59,7 +72,7 @@ pub async fn socket(op: NetOp) -> io::Result<OwnedFd> {
|
||||
unreachable!("socket backend called with non-socket op");
|
||||
};
|
||||
|
||||
offload(move || socket_sync(domain, socket_type, protocol, flags)).await
|
||||
socket_sync(domain, socket_type, protocol, flags)
|
||||
}
|
||||
|
||||
pub async fn connect(op: NetOp) -> io::Result<()> {
|
||||
@@ -67,7 +80,7 @@ pub async fn connect(op: NetOp) -> io::Result<()> {
|
||||
unreachable!("connect backend called with non-connect op");
|
||||
};
|
||||
|
||||
offload(move || connect_sync(fd, RawSocketAddr::from_socket_addr(addr))).await
|
||||
connect_async(fd, RawSocketAddr::from_socket_addr(addr)).await
|
||||
}
|
||||
|
||||
pub async fn bind(op: NetOp) -> io::Result<()> {
|
||||
@@ -75,7 +88,7 @@ pub async fn bind(op: NetOp) -> io::Result<()> {
|
||||
unreachable!("bind backend called with non-bind op");
|
||||
};
|
||||
|
||||
offload(move || bind_sync(fd, RawSocketAddr::from_socket_addr(addr))).await
|
||||
bind_sync(fd, RawSocketAddr::from_socket_addr(addr))
|
||||
}
|
||||
|
||||
pub async fn listen(op: NetOp) -> io::Result<()> {
|
||||
@@ -83,7 +96,7 @@ pub async fn listen(op: NetOp) -> io::Result<()> {
|
||||
unreachable!("listen backend called with non-listen op");
|
||||
};
|
||||
|
||||
offload(move || listen_sync(fd, backlog)).await
|
||||
listen_sync(fd, backlog)
|
||||
}
|
||||
|
||||
pub async fn accept(op: NetOp) -> io::Result<AcceptedSocket> {
|
||||
@@ -91,7 +104,7 @@ pub async fn accept(op: NetOp) -> io::Result<AcceptedSocket> {
|
||||
unreachable!("accept backend called with non-accept op");
|
||||
};
|
||||
|
||||
offload(move || accept_sync(fd)).await
|
||||
accept_async(fd).await
|
||||
}
|
||||
|
||||
pub async fn send(op: NetOp) -> io::Result<usize> {
|
||||
@@ -99,7 +112,7 @@ pub async fn send(op: NetOp) -> io::Result<usize> {
|
||||
unreachable!("send backend called with non-send op");
|
||||
};
|
||||
|
||||
offload(move || send_sync(fd, data, flags)).await
|
||||
send_async(fd, data, flags).await
|
||||
}
|
||||
|
||||
pub async fn send_to(op: NetOp) -> io::Result<usize> {
|
||||
@@ -113,7 +126,7 @@ pub async fn send_to(op: NetOp) -> io::Result<usize> {
|
||||
unreachable!("send_to backend called with non-send_to op");
|
||||
};
|
||||
|
||||
offload(move || send_to_sync(fd, target, data, flags)).await
|
||||
send_to_async(fd, target, data, flags).await
|
||||
}
|
||||
|
||||
pub async fn recv(op: NetOp) -> io::Result<Vec<u8>> {
|
||||
@@ -121,7 +134,7 @@ pub async fn recv(op: NetOp) -> io::Result<Vec<u8>> {
|
||||
unreachable!("recv backend called with non-recv op");
|
||||
};
|
||||
|
||||
offload(move || recv_sync(fd, len, flags)).await
|
||||
recv_async(fd, len, flags).await
|
||||
}
|
||||
|
||||
pub async fn recv_from(op: NetOp) -> io::Result<ReceivedDatagram> {
|
||||
@@ -129,7 +142,7 @@ pub async fn recv_from(op: NetOp) -> io::Result<ReceivedDatagram> {
|
||||
unreachable!("recv_from backend called with non-recv_from op");
|
||||
};
|
||||
|
||||
offload(move || recv_from_sync(fd, len, flags)).await
|
||||
recv_from_async(fd, len, flags).await
|
||||
}
|
||||
|
||||
pub async fn shutdown(op: NetOp) -> io::Result<()> {
|
||||
@@ -137,7 +150,7 @@ pub async fn shutdown(op: NetOp) -> io::Result<()> {
|
||||
unreachable!("shutdown backend called with non-shutdown op");
|
||||
};
|
||||
|
||||
offload(move || shutdown_sync(fd, how)).await
|
||||
shutdown_sync(fd, how)
|
||||
}
|
||||
|
||||
pub async fn close(op: NetOp) -> io::Result<()> {
|
||||
@@ -145,7 +158,7 @@ pub async fn close(op: NetOp) -> io::Result<()> {
|
||||
unreachable!("close backend called with non-close op");
|
||||
};
|
||||
|
||||
offload(move || close_sync(fd)).await
|
||||
close_sync(fd)
|
||||
}
|
||||
|
||||
pub async fn connect_stream(addr: SocketAddr) -> io::Result<OwnedFd> {
|
||||
@@ -234,11 +247,9 @@ async fn bind_datagram_inner(addr: SocketAddr) -> io::Result<OwnedFd> {
|
||||
}
|
||||
|
||||
pub async fn duplicate(fd: RawFd) -> io::Result<OwnedFd> {
|
||||
offload(move || {
|
||||
let duplicated = cvt(unsafe { libc::fcntl(fd, libc::F_DUPFD_CLOEXEC, 0) })?;
|
||||
Ok(unsafe { OwnedFd::from_raw_fd(duplicated) })
|
||||
})
|
||||
.await
|
||||
let duplicated = cvt(unsafe { libc::fcntl(fd, libc::F_DUPFD_CLOEXEC, 0) })?;
|
||||
set_nonblocking(duplicated)?;
|
||||
Ok(unsafe { OwnedFd::from_raw_fd(duplicated) })
|
||||
}
|
||||
|
||||
pub async fn recv_timeout(
|
||||
@@ -247,11 +258,7 @@ pub async fn recv_timeout(
|
||||
flags: i32,
|
||||
timeout: Duration,
|
||||
) -> io::Result<Vec<u8>> {
|
||||
offload(move || {
|
||||
wait_for_fd(fd, libc::POLLIN, timeout)?;
|
||||
recv_sync(fd, len, flags)
|
||||
})
|
||||
.await
|
||||
io_timeout(timeout, recv_async(fd, len, flags)).await
|
||||
}
|
||||
|
||||
pub async fn send_timeout(
|
||||
@@ -260,11 +267,7 @@ pub async fn send_timeout(
|
||||
flags: i32,
|
||||
timeout: Duration,
|
||||
) -> io::Result<usize> {
|
||||
offload(move || {
|
||||
wait_for_fd(fd, libc::POLLOUT, timeout)?;
|
||||
send_sync(fd, data, flags)
|
||||
})
|
||||
.await
|
||||
io_timeout(timeout, send_async(fd, data, flags)).await
|
||||
}
|
||||
|
||||
pub async fn recv_from_timeout(
|
||||
@@ -273,11 +276,7 @@ pub async fn recv_from_timeout(
|
||||
flags: i32,
|
||||
timeout: Duration,
|
||||
) -> io::Result<ReceivedDatagram> {
|
||||
offload(move || {
|
||||
wait_for_fd(fd, libc::POLLIN, timeout)?;
|
||||
recv_from_sync(fd, len, flags)
|
||||
})
|
||||
.await
|
||||
io_timeout(timeout, recv_from_async(fd, len, flags)).await
|
||||
}
|
||||
|
||||
pub async fn send_to_timeout(
|
||||
@@ -287,15 +286,21 @@ pub async fn send_to_timeout(
|
||||
flags: i32,
|
||||
timeout: Duration,
|
||||
) -> io::Result<usize> {
|
||||
offload(move || {
|
||||
wait_for_fd(fd, libc::POLLOUT, timeout)?;
|
||||
send_to_sync(fd, target, data, flags)
|
||||
})
|
||||
.await
|
||||
io_timeout(timeout, send_to_async(fd, target, data, flags)).await
|
||||
}
|
||||
|
||||
pub async fn connect_stream_timeout(addr: SocketAddr, timeout: Duration) -> io::Result<OwnedFd> {
|
||||
offload(move || connect_stream_timeout_sync(addr, timeout)).await
|
||||
let fd = socket_sync(socket_domain(addr), libc::SOCK_STREAM, 0, 0)?;
|
||||
if let Err(error) = io_timeout(
|
||||
timeout,
|
||||
connect_async(fd.as_raw_fd(), RawSocketAddr::from_socket_addr(addr)),
|
||||
)
|
||||
.await
|
||||
{
|
||||
drop(fd);
|
||||
return Err(error);
|
||||
}
|
||||
Ok(fd)
|
||||
}
|
||||
|
||||
pub fn local_addr(fd: RawFd) -> io::Result<SocketAddr> {
|
||||
@@ -396,6 +401,15 @@ async fn offload<T: Send + 'static>(
|
||||
future.await
|
||||
}
|
||||
|
||||
async fn io_timeout<T>(
|
||||
timeout: Duration,
|
||||
future: impl Future<Output = io::Result<T>>,
|
||||
) -> io::Result<T> {
|
||||
crate::time::timeout(timeout, future)
|
||||
.await
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::TimedOut, "operation timed out"))?
|
||||
}
|
||||
|
||||
fn socket_domain(addr: SocketAddr) -> i32 {
|
||||
match addr {
|
||||
SocketAddr::V4(_) => libc::AF_INET,
|
||||
@@ -573,11 +587,28 @@ impl RawSocketAddr {
|
||||
fn socket_sync(domain: i32, socket_type: i32, protocol: i32, _flags: u32) -> io::Result<OwnedFd> {
|
||||
let fd = cvt(unsafe { libc::socket(domain, socket_type, protocol) })?;
|
||||
set_cloexec(fd)?;
|
||||
set_nonblocking(fd)?;
|
||||
Ok(unsafe { OwnedFd::from_raw_fd(fd) })
|
||||
}
|
||||
|
||||
fn connect_sync(fd: RawFd, addr: RawSocketAddr) -> io::Result<()> {
|
||||
cvt(unsafe { libc::connect(fd, addr.as_ptr(), addr.len()) }).map(|_| ())
|
||||
async fn connect_async(fd: RawFd, addr: RawSocketAddr) -> io::Result<()> {
|
||||
loop {
|
||||
let result = unsafe { libc::connect(fd, addr.as_ptr(), addr.len()) };
|
||||
if result == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let error = io::Error::last_os_error();
|
||||
match error.raw_os_error() {
|
||||
Some(libc::EINTR) => continue,
|
||||
Some(libc::EINPROGRESS) | Some(libc::EALREADY) => {
|
||||
crate::sys::current::fd::wait_writable(fd).await?;
|
||||
return socket_error(fd);
|
||||
}
|
||||
Some(libc::EISCONN) => return Ok(()),
|
||||
_ => return Err(error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn bind_sync(fd: RawFd, addr: RawSocketAddr) -> io::Result<()> {
|
||||
@@ -602,12 +633,59 @@ fn accept_sync(fd: RawFd) -> io::Result<AcceptedSocket> {
|
||||
})
|
||||
}
|
||||
|
||||
fn send_sync(fd: RawFd, data: Vec<u8>, flags: i32) -> io::Result<usize> {
|
||||
async fn accept_async(fd: RawFd) -> io::Result<AcceptedSocket> {
|
||||
loop {
|
||||
match accept_sync(fd) {
|
||||
Ok(socket) => {
|
||||
set_nonblocking(socket.fd)?;
|
||||
return Ok(socket);
|
||||
}
|
||||
Err(error) if error.kind() == io::ErrorKind::WouldBlock => {
|
||||
crate::sys::current::fd::wait_readable(fd).await?;
|
||||
}
|
||||
Err(error) if error.kind() == io::ErrorKind::Interrupted => {}
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn send_async(fd: RawFd, data: Vec<u8>, flags: i32) -> io::Result<usize> {
|
||||
loop {
|
||||
match send_slice_sync(fd, &data, flags) {
|
||||
Ok(written) => return Ok(written),
|
||||
Err(error) if error.kind() == io::ErrorKind::WouldBlock => {
|
||||
crate::sys::current::fd::wait_writable(fd).await?;
|
||||
}
|
||||
Err(error) if error.kind() == io::ErrorKind::Interrupted => {}
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn send_slice_sync(fd: RawFd, data: &[u8], flags: i32) -> io::Result<usize> {
|
||||
let written = unsafe { libc::send(fd, data.as_ptr().cast::<c_void>(), data.len(), flags) };
|
||||
cvt_long(written).map(|written| written as usize)
|
||||
}
|
||||
|
||||
fn send_to_sync(fd: RawFd, target: SocketAddr, data: Vec<u8>, flags: i32) -> io::Result<usize> {
|
||||
async fn send_to_async(
|
||||
fd: RawFd,
|
||||
target: SocketAddr,
|
||||
data: Vec<u8>,
|
||||
flags: i32,
|
||||
) -> io::Result<usize> {
|
||||
loop {
|
||||
match send_to_slice_sync(fd, target, &data, flags) {
|
||||
Ok(written) => return Ok(written),
|
||||
Err(error) if error.kind() == io::ErrorKind::WouldBlock => {
|
||||
crate::sys::current::fd::wait_writable(fd).await?;
|
||||
}
|
||||
Err(error) if error.kind() == io::ErrorKind::Interrupted => {}
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn send_to_slice_sync(fd: RawFd, target: SocketAddr, data: &[u8], flags: i32) -> io::Result<usize> {
|
||||
let addr = RawSocketAddr::from_socket_addr(target);
|
||||
let written = unsafe {
|
||||
libc::sendto(
|
||||
@@ -622,6 +700,19 @@ fn send_to_sync(fd: RawFd, target: SocketAddr, data: Vec<u8>, flags: i32) -> io:
|
||||
cvt_long(written).map(|written| written as usize)
|
||||
}
|
||||
|
||||
async fn recv_async(fd: RawFd, len: usize, flags: i32) -> io::Result<Vec<u8>> {
|
||||
loop {
|
||||
match recv_sync(fd, len, flags) {
|
||||
Ok(data) => return Ok(data),
|
||||
Err(error) if error.kind() == io::ErrorKind::WouldBlock => {
|
||||
crate::sys::current::fd::wait_readable(fd).await?;
|
||||
}
|
||||
Err(error) if error.kind() == io::ErrorKind::Interrupted => {}
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn recv_sync(fd: RawFd, len: usize, flags: i32) -> io::Result<Vec<u8>> {
|
||||
let mut data = vec![0u8; len];
|
||||
let read = unsafe { libc::recv(fd, data.as_mut_ptr().cast::<c_void>(), len, flags) };
|
||||
@@ -630,6 +721,19 @@ fn recv_sync(fd: RawFd, len: usize, flags: i32) -> io::Result<Vec<u8>> {
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
async fn recv_from_async(fd: RawFd, len: usize, flags: i32) -> io::Result<ReceivedDatagram> {
|
||||
loop {
|
||||
match recv_from_sync(fd, len, flags) {
|
||||
Ok(datagram) => return Ok(datagram),
|
||||
Err(error) if error.kind() == io::ErrorKind::WouldBlock => {
|
||||
crate::sys::current::fd::wait_readable(fd).await?;
|
||||
}
|
||||
Err(error) if error.kind() == io::ErrorKind::Interrupted => {}
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn recv_from_sync(fd: RawFd, len: usize, flags: i32) -> io::Result<ReceivedDatagram> {
|
||||
let mut data = vec![0u8; len];
|
||||
let mut storage = MaybeUninit::<libc::sockaddr_storage>::zeroed();
|
||||
@@ -659,79 +763,6 @@ fn close_sync(fd: RawFd) -> io::Result<()> {
|
||||
cvt(unsafe { libc::close(fd) }).map(|_| ())
|
||||
}
|
||||
|
||||
fn connect_stream_timeout_sync(addr: SocketAddr, timeout: Duration) -> io::Result<OwnedFd> {
|
||||
let fd = cvt(unsafe { libc::socket(socket_domain(addr), libc::SOCK_STREAM, 0) })?;
|
||||
set_cloexec(fd)?;
|
||||
set_nonblocking(fd)?;
|
||||
|
||||
let raw = RawSocketAddr::from_socket_addr(addr);
|
||||
let connect_result = unsafe { libc::connect(fd, raw.as_ptr(), raw.len()) };
|
||||
if connect_result < 0 {
|
||||
let error = io::Error::last_os_error();
|
||||
if error.raw_os_error() != Some(libc::EINPROGRESS) {
|
||||
let _ = unsafe { libc::close(fd) };
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
|
||||
let wait = wait_for_fd(fd, libc::POLLOUT, timeout);
|
||||
if let Err(error) = wait {
|
||||
let _ = unsafe { libc::close(fd) };
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
let mut so_error: libc::c_int = 0;
|
||||
let mut len = std::mem::size_of::<libc::c_int>() as libc::socklen_t;
|
||||
if unsafe {
|
||||
libc::getsockopt(
|
||||
fd,
|
||||
libc::SOL_SOCKET,
|
||||
libc::SO_ERROR,
|
||||
&mut so_error as *mut libc::c_int as *mut c_void,
|
||||
&mut len,
|
||||
)
|
||||
} < 0
|
||||
{
|
||||
let error = io::Error::last_os_error();
|
||||
let _ = unsafe { libc::close(fd) };
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
if so_error != 0 {
|
||||
let _ = unsafe { libc::close(fd) };
|
||||
return Err(io::Error::from_raw_os_error(so_error));
|
||||
}
|
||||
|
||||
clear_nonblocking(fd)?;
|
||||
Ok(unsafe { OwnedFd::from_raw_fd(fd) })
|
||||
}
|
||||
|
||||
fn wait_for_fd(fd: RawFd, events: i16, timeout: Duration) -> io::Result<()> {
|
||||
let timeout_ms = timeout.as_millis().min(i32::MAX as u128) as i32;
|
||||
let mut pfd = libc::pollfd {
|
||||
fd,
|
||||
events,
|
||||
revents: 0,
|
||||
};
|
||||
loop {
|
||||
let result = unsafe { libc::poll(&mut pfd, 1, timeout_ms) };
|
||||
if result > 0 {
|
||||
return Ok(());
|
||||
}
|
||||
if result == 0 {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::TimedOut,
|
||||
"operation timed out",
|
||||
));
|
||||
}
|
||||
let error = io::Error::last_os_error();
|
||||
if error.kind() == io::ErrorKind::Interrupted {
|
||||
continue;
|
||||
}
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_cloexec(fd: RawFd) -> io::Result<()> {
|
||||
let flags = cvt(unsafe { libc::fcntl(fd, libc::F_GETFD) })?;
|
||||
cvt(unsafe { libc::fcntl(fd, libc::F_SETFD, flags | libc::FD_CLOEXEC) })?;
|
||||
@@ -744,12 +775,6 @@ fn set_nonblocking(fd: RawFd) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn clear_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 should_try_ipv4_loopback(addr: SocketAddr, error: &io::Error) -> bool {
|
||||
matches!(addr, SocketAddr::V6(v6) if v6.ip().is_loopback())
|
||||
&& matches!(
|
||||
@@ -758,6 +783,25 @@ fn should_try_ipv4_loopback(addr: SocketAddr, error: &io::Error) -> bool {
|
||||
)
|
||||
}
|
||||
|
||||
fn socket_error(fd: RawFd) -> io::Result<()> {
|
||||
let mut so_error: libc::c_int = 0;
|
||||
let mut len = std::mem::size_of::<libc::c_int>() as libc::socklen_t;
|
||||
cvt(unsafe {
|
||||
libc::getsockopt(
|
||||
fd,
|
||||
libc::SOL_SOCKET,
|
||||
libc::SO_ERROR,
|
||||
&mut so_error as *mut libc::c_int as *mut c_void,
|
||||
&mut len,
|
||||
)
|
||||
})?;
|
||||
if so_error == 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(io::Error::from_raw_os_error(so_error))
|
||||
}
|
||||
}
|
||||
|
||||
fn localhost_v4(addr: SocketAddr) -> SocketAddr {
|
||||
SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, addr.port()))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user