use std::future::Future;
use std::io;
use std::pin::Pin;
use std::sync::{Arc, Mutex, PoisonError};
use std::task::{Context, Poll, Wake, Waker};
use std::time::{Duration, Instant};
use foundation_core::io::ioutils::SharedByteBufferStream;
use foundation_netio::native::readiness::IoInterest;
use foundation_netio::netcap::RawStream;
use crate::shared::router::DeadlineTimer;
use crate::shared::transport::BoxFuture;
#[derive(Default)]
struct Waiters {
read: Vec<(Waker, Instant)>,
write: Vec<(Waker, Instant)>,
timers: Vec<(Waker, Instant)>,
hangup: Vec<Waker>,
}
impl Waiters {
fn list(&mut self, interest: IoInterest) -> &mut Vec<(Waker, Instant)> {
match interest {
IoInterest::Readable => &mut self.read,
IoInterest::Writable => &mut self.write,
}
}
fn earliest_deadline(&self) -> Option<Instant> {
self.read
.iter()
.chain(self.write.iter())
.chain(self.timers.iter())
.map(|(_, deadline)| *deadline)
.min()
}
}
fn register_in(list: &mut Vec<(Waker, Instant)>, waker: &Waker, deadline: Instant) {
if let Some(entry) = list.iter_mut().find(|(w, _)| w.will_wake(waker)) {
entry.1 = deadline;
} else {
list.push((waker.clone(), deadline));
}
}
pub(crate) struct ConnIo {
#[cfg(unix)]
fd: Option<std::os::unix::io::RawFd>,
#[cfg(unix)]
wake_rx: std::os::unix::net::UnixStream,
#[cfg(unix)]
wake_tx: Arc<WakeTx>,
waiters: Mutex<Waiters>,
}
impl ConnIo {
pub(crate) fn new(conn: &SharedByteBufferStream<RawStream>) -> io::Result<Self> {
#[cfg(unix)]
{
use std::os::unix::io::AsRawFd;
let fd = conn.with_inner_ref(|raw: &RawStream| {
let fd = raw.as_raw_fd();
(fd >= 0 && raw.readiness(IoInterest::Readable).is_some()).then_some(fd)
});
let (wake_rx, wake_tx) = std::os::unix::net::UnixStream::pair()?;
wake_rx.set_nonblocking(true)?;
wake_tx.set_nonblocking(true)?;
Ok(Self {
fd,
wake_rx,
wake_tx: Arc::new(WakeTx(wake_tx)),
waiters: Mutex::new(Waiters::default()),
})
}
#[cfg(not(unix))]
{
let _ = conn;
Ok(Self {
waiters: Mutex::new(Waiters::default()),
})
}
}
pub(crate) fn ready(&self, interest: IoInterest, timeout: Duration) -> IoReady<'_> {
IoReady {
io: self,
interest,
timeout,
deadline: None,
}
}
fn waiters(&self) -> std::sync::MutexGuard<'_, Waiters> {
self.waiters.lock().unwrap_or_else(PoisonError::into_inner)
}
fn register(&self, interest: IoInterest, waker: &Waker, deadline: Instant) {
register_in(self.waiters().list(interest), waker, deadline);
}
pub(crate) fn sleep_until(&self, deadline: Instant) -> Sleep<'_> {
Sleep { io: self, deadline }
}
pub(crate) fn hangup(&self) -> Hangup<'_> {
Hangup { io: self }
}
}
impl DeadlineTimer for ConnIo {
fn sleep_until(&self, deadline: Instant) -> BoxFuture<'_, ()> {
Box::pin(ConnIo::sleep_until(self, deadline))
}
}
pub(crate) struct Sleep<'a> {
io: &'a ConnIo,
deadline: Instant,
}
impl Future for Sleep<'_> {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
if Instant::now() >= self.deadline {
return Poll::Ready(());
}
register_in(&mut self.io.waiters().timers, cx.waker(), self.deadline);
Poll::Pending
}
}
pub(crate) struct Hangup<'a> {
io: &'a ConnIo,
}
impl Future for Hangup<'_> {
type Output = io::Result<()>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.io.gone_now() {
Ok(true) => return Poll::Ready(Ok(())),
Ok(false) => {}
Err(e) => return Poll::Ready(Err(e)),
}
let mut waiters = self.io.waiters();
if !waiters.hangup.iter().any(|w| w.will_wake(cx.waker())) {
waiters.hangup.push(cx.waker().clone());
}
Poll::Pending
}
}
#[cfg(unix)]
impl ConnIo {
fn fd(&self) -> io::Result<std::os::unix::io::RawFd> {
self.fd.ok_or_else(|| {
io::Error::new(
io::ErrorKind::Unsupported,
"this transport cannot report socket readiness, so the RPC cannot wait for the peer",
)
})
}
fn gone_now(&self) -> io::Result<bool> {
let mut fds = [libc::pollfd {
fd: self.fd()?,
events: HANGUP_EVENTS,
revents: 0,
}];
poll_fds(&mut fds, 0)?;
Ok(fds[0].revents & (HANGUP_EVENTS | GONE_EVENTS) != 0)
}
fn ready_now(&self, interest: IoInterest) -> io::Result<bool> {
let fd = self.fd()?;
let mut fds = [libc::pollfd {
fd,
events: poll_events(interest),
revents: 0,
}];
Ok(poll_fds(&mut fds, 0)? > 0)
}
pub(crate) fn block_on<F: Future>(&self, future: F) -> F::Output {
let waker = Waker::from(self.wake_tx.clone());
let mut cx = Context::from_waker(&waker);
let mut future = std::pin::pin!(future);
loop {
if let Poll::Ready(value) = future.as_mut().poll(&mut cx) {
return value;
}
self.wait();
}
}
fn wait(&self) {
use std::io::Read;
let (events, deadline) = {
let waiters = self.waiters();
let mut events = 0;
if !waiters.read.is_empty() {
events |= poll_events(IoInterest::Readable);
}
if !waiters.write.is_empty() {
events |= poll_events(IoInterest::Writable);
}
if !waiters.hangup.is_empty() {
events |= HANGUP_EVENTS;
}
(events, waiters.earliest_deadline())
};
let timeout_ms = deadline.map_or(-1, |deadline| {
let left = deadline.saturating_duration_since(Instant::now());
i32::try_from(left.as_millis() + 1).unwrap_or(i32::MAX)
});
let mut fds = [
libc::pollfd {
fd: std::os::unix::io::AsRawFd::as_raw_fd(&self.wake_rx),
events: libc::POLLIN,
revents: 0,
},
libc::pollfd {
fd: self.fd.unwrap_or(-1),
events,
revents: 0,
},
];
let count = if events != 0 && self.fd.is_some() {
2
} else {
1
};
if let Err(e) = poll_fds(&mut fds[..count], timeout_ms) {
tracing::warn!(error = %e, "poll(2) on the connection failed; waking all waiters");
let mut waiters = self.waiters();
let Waiters {
read,
write,
timers,
hangup,
} = &mut *waiters;
for (waker, _) in read.drain(..).chain(write.drain(..)).chain(timers.drain(..)) {
waker.wake();
}
for waker in hangup.drain(..) {
waker.wake();
}
return;
}
let mut sink = [0u8; 64];
while matches!((&self.wake_rx).read(&mut sink), Ok(n) if n > 0) {}
let fired = fds[1].revents;
let now = Instant::now();
let mut waiters = self.waiters();
let read_fired = count == 2 && fired & (libc::POLLIN | GONE_EVENTS) != 0;
let write_fired = count == 2 && fired & (libc::POLLOUT | GONE_EVENTS) != 0;
let hangup_fired = count == 2 && fired & (HANGUP_EVENTS | GONE_EVENTS) != 0;
wake_list(&mut waiters.read, read_fired, now);
wake_list(&mut waiters.write, write_fired, now);
wake_list(&mut waiters.timers, false, now);
if hangup_fired {
for waker in waiters.hangup.drain(..) {
waker.wake();
}
}
}
}
#[cfg(unix)]
const GONE_EVENTS: libc::c_short = libc::POLLERR | libc::POLLHUP | libc::POLLNVAL;
#[cfg(any(target_os = "linux", target_os = "android"))]
const HANGUP_EVENTS: libc::c_short = libc::POLLRDHUP;
#[cfg(all(unix, not(any(target_os = "linux", target_os = "android"))))]
const HANGUP_EVENTS: libc::c_short = 0;
#[cfg(not(unix))]
impl ConnIo {
fn gone_now(&self) -> io::Result<bool> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"watching a connection for a hangup is only supported on unix",
))
}
fn ready_now(&self, _interest: IoInterest) -> io::Result<bool> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"waiting on a connection is only supported on unix",
))
}
pub(crate) fn block_on<F: Future>(&self, future: F) -> F::Output {
struct ThreadWaker(std::thread::Thread);
impl Wake for ThreadWaker {
fn wake(self: Arc<Self>) {
self.0.unpark();
}
}
let waker = Waker::from(Arc::new(ThreadWaker(std::thread::current())));
let mut cx = Context::from_waker(&waker);
let mut future = std::pin::pin!(future);
loop {
match future.as_mut().poll(&mut cx) {
Poll::Ready(value) => return value,
Poll::Pending => {
let earliest = self.waiters().earliest_deadline();
match earliest {
Some(deadline) => std::thread::park_timeout(
deadline.saturating_duration_since(Instant::now()),
),
None => std::thread::park(),
}
let now = Instant::now();
self.waiters().timers.retain(|(waker, deadline)| {
if *deadline <= now {
waker.wake_by_ref();
false
} else {
true
}
});
}
}
}
}
}
#[cfg(unix)]
fn wake_list(list: &mut Vec<(Waker, Instant)>, fired: bool, now: Instant) {
list.retain(|(waker, deadline)| {
if fired || *deadline <= now {
waker.wake_by_ref();
false
} else {
true
}
});
}
#[cfg(unix)]
fn poll_events(interest: IoInterest) -> libc::c_short {
match interest {
IoInterest::Readable => libc::POLLIN,
IoInterest::Writable => libc::POLLOUT,
}
}
#[cfg(unix)]
fn poll_fds(fds: &mut [libc::pollfd], timeout_ms: i32) -> io::Result<usize> {
loop {
let len = libc::nfds_t::try_from(fds.len()).unwrap_or(libc::nfds_t::MAX);
let rc = unsafe { libc::poll(fds.as_mut_ptr(), len, timeout_ms) };
if rc >= 0 {
return Ok(usize::try_from(rc).unwrap_or(0));
}
let err = io::Error::last_os_error();
if err.kind() != io::ErrorKind::Interrupted {
return Err(err);
}
}
}
#[cfg(unix)]
struct WakeTx(std::os::unix::net::UnixStream);
#[cfg(unix)]
impl Wake for WakeTx {
fn wake(self: Arc<Self>) {
self.wake_by_ref();
}
fn wake_by_ref(self: &Arc<Self>) {
use std::io::Write;
match (&self.0).write(&[1]) {
Ok(_) => {}
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {}
Err(e) => tracing::warn!(error = %e, "could not wake the connection driver"),
}
}
}
pub(crate) struct IoReady<'a> {
io: &'a ConnIo,
interest: IoInterest,
timeout: Duration,
deadline: Option<Instant>,
}
impl Future for IoReady<'_> {
type Output = io::Result<()>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let timeout = self.timeout;
let deadline = *self
.deadline
.get_or_insert_with(|| Instant::now() + timeout);
match self.io.ready_now(self.interest) {
Ok(true) => return Poll::Ready(Ok(())),
Ok(false) => {}
Err(e) => return Poll::Ready(Err(e)),
}
if Instant::now() >= deadline {
return Poll::Ready(Err(io::Error::new(
io::ErrorKind::TimedOut,
format!(
"the connection did not become {} within {timeout:?}",
self.interest
),
)));
}
self.io.register(self.interest, cx.waker(), deadline);
Poll::Pending
}
}