use core::fmt;
use core::future::poll_fn;
use core::task::{Context, Poll, ready};
use std::io;
use std::os::fd::AsFd;
use super::caller::{self, Marks};
use super::source::Kind;
use super::{Handle, Interest, Ready};
pub struct AsyncFd<T: AsFd> {
handle: Handle<T>,
interest: Interest,
}
pub struct AsyncFdReadyGuard<'a, T: AsFd> {
fd: &'a AsyncFd<T>,
marks: Marks,
}
pub struct AsyncFdReadyMutGuard<'a, T: AsFd> {
fd: &'a mut AsyncFd<T>,
marks: Marks,
}
#[derive(Debug)]
pub struct TryIoError(());
impl<T: AsFd> AsyncFd<T> {
pub fn new(inner: T) -> io::Result<Self> {
Self::with_interest(inner, Interest::READABLE | Interest::WRITABLE)
}
pub fn with_interest(inner: T, interest: Interest) -> io::Result<Self> {
Ok(Self {
handle: Handle::from_nonblocking(inner, Kind::Fd),
interest,
})
}
pub fn get_ref(&self) -> &T {
self.handle.get_ref()
}
pub fn get_mut(&mut self) -> &mut T {
self.handle.get_mut()
}
pub fn into_inner(self) -> T {
self.handle.into_inner()
}
pub fn poll_read_ready<'a>(
&'a self,
cx: &mut Context<'_>,
) -> Poll<io::Result<AsyncFdReadyGuard<'a, T>>> {
let tick = ready!(self.handle.source().poll_fd_read_ready(cx))?;
Poll::Ready(Ok(AsyncFdReadyGuard {
fd: self,
marks: Marks {
ready: Ready::READABLE,
read: Some(tick),
write: None,
},
}))
}
pub fn poll_write_ready<'a>(
&'a self,
cx: &mut Context<'_>,
) -> Poll<io::Result<AsyncFdReadyGuard<'a, T>>> {
let tick = ready!(self.handle.source().poll_fd_write_ready(cx))?;
Poll::Ready(Ok(AsyncFdReadyGuard {
fd: self,
marks: Marks {
ready: Ready::WRITABLE,
read: None,
write: Some(tick),
},
}))
}
fn sides(&self, interest: Interest) -> io::Result<(bool, bool)> {
let (read, write) = caller::sides(interest);
let (read, write) = (
read && self.interest.is_readable(),
write && self.interest.is_writable(),
);
if !read && !write {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"the descriptor is not watched for that readiness",
));
}
Ok((read, write))
}
fn poll_marks(&self, cx: &mut Context<'_>, interest: Interest) -> Poll<io::Result<Marks>> {
let (read, write) = self.sides(interest)?;
caller::poll_marks(self.handle.source(), cx, read, write)
}
pub async fn readable(&self) -> io::Result<AsyncFdReadyGuard<'_, T>> {
self.ready(Interest::READABLE).await
}
pub async fn writable(&self) -> io::Result<AsyncFdReadyGuard<'_, T>> {
self.ready(Interest::WRITABLE).await
}
pub async fn ready(&self, interest: Interest) -> io::Result<AsyncFdReadyGuard<'_, T>> {
let marks = poll_fn(|cx| self.poll_marks(cx, interest)).await?;
Ok(AsyncFdReadyGuard { fd: self, marks })
}
pub async fn readable_mut(&mut self) -> io::Result<AsyncFdReadyMutGuard<'_, T>> {
self.ready_mut(Interest::READABLE).await
}
pub async fn writable_mut(&mut self) -> io::Result<AsyncFdReadyMutGuard<'_, T>> {
self.ready_mut(Interest::WRITABLE).await
}
pub async fn ready_mut(
&mut self,
interest: Interest,
) -> io::Result<AsyncFdReadyMutGuard<'_, T>> {
let marks = poll_fn(|cx| self.poll_marks(cx, interest)).await?;
Ok(AsyncFdReadyMutGuard { fd: self, marks })
}
pub async fn async_io<R>(
&self,
interest: Interest,
mut f: impl FnMut(&T) -> io::Result<R>,
) -> io::Result<R> {
let sides = self.sides(interest)?;
caller::async_io(self.handle.source(), sides, || f(self.get_ref())).await
}
pub fn try_io<R>(
&self,
interest: Interest,
f: impl FnOnce(&T) -> io::Result<R>,
) -> io::Result<R> {
caller::try_io(self.handle.source(), self.sides(interest)?, || {
f(self.get_ref())
})
}
fn clear(&self, marks: Marks) {
caller::clear(self.handle.source(), marks);
}
}
macro_rules! guard {
($guard:ident) => {
impl<T: AsFd> $guard<'_, T> {
pub fn ready(&self) -> Ready {
self.marks.ready
}
pub fn clear_ready(&mut self) {
self.fd.clear(self.marks);
self.marks.ready = Ready::EMPTY;
}
pub fn clear_ready_matching(&mut self, ready: Ready) {
let mut marks = self.marks;
if !ready.is_readable() {
marks.read = None;
}
if !ready.is_writable() {
marks.write = None;
}
self.fd.clear(marks);
if ready.is_readable() {
self.marks.read = None;
}
if ready.is_writable() {
self.marks.write = None;
}
self.marks.ready = Ready::EMPTY;
if self.marks.read.is_some() {
self.marks.ready = self.marks.ready | Ready::READABLE;
}
if self.marks.write.is_some() {
self.marks.ready = self.marks.ready | Ready::WRITABLE;
}
}
pub fn retain_ready(&mut self) {}
pub fn get_inner(&self) -> &T {
self.fd.get_ref()
}
}
impl<T: AsFd> fmt::Debug for $guard<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct(stringify!($guard))
.field("ready", &self.marks.ready)
.finish_non_exhaustive()
}
}
};
}
guard!(AsyncFdReadyGuard);
guard!(AsyncFdReadyMutGuard);
impl<'a, T: AsFd> AsyncFdReadyGuard<'a, T> {
pub fn try_io<R>(
&mut self,
f: impl FnOnce(&'a AsyncFd<T>) -> io::Result<R>,
) -> Result<io::Result<R>, TryIoError> {
match f(self.fd) {
Err(err) if err.kind() == io::ErrorKind::WouldBlock => {
self.clear_ready();
Err(TryIoError(()))
}
result => Ok(result),
}
}
pub fn get_ref(&self) -> &'a AsyncFd<T> {
self.fd
}
}
impl<T: AsFd> AsyncFdReadyMutGuard<'_, T> {
pub fn try_io<R>(
&mut self,
f: impl FnOnce(&mut AsyncFd<T>) -> io::Result<R>,
) -> Result<io::Result<R>, TryIoError> {
match f(self.fd) {
Err(err) if err.kind() == io::ErrorKind::WouldBlock => {
self.clear_ready();
Err(TryIoError(()))
}
result => Ok(result),
}
}
pub fn get_ref(&self) -> &AsyncFd<T> {
self.fd
}
pub fn get_mut(&mut self) -> &mut AsyncFd<T> {
self.fd
}
pub fn get_inner_mut(&mut self) -> &mut T {
self.fd.get_mut()
}
}
impl<T: AsFd + fmt::Debug> fmt::Debug for AsyncFd<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AsyncFd")
.field("inner", self.get_ref())
.finish_non_exhaustive()
}
}
impl fmt::Display for TryIoError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("the call would block")
}
}
impl std::error::Error for TryIoError {}
#[cfg(test)]
mod tests {
use std::io::{Read, Write};
use std::os::unix::net::UnixStream;
use std::time::{Duration, Instant};
use super::*;
fn on_ring() -> bool {
crate::stats()
.shards
.iter()
.all(|shard| shard.backend == crate::IoBackend::IoUring)
}
#[test]
fn an_edge_after_the_call_survives_the_clear() {
crate::block_on(async {
let (a, mut b) = UnixStream::pair().unwrap();
a.set_nonblocking(true).unwrap();
let fd = AsyncFd::with_interest(a, Interest::READABLE).unwrap();
let mut buf = [0u8; 8];
let mut guard = fd.readable().await.unwrap();
assert!(guard.try_io(|fd| fd.get_ref().read(&mut buf)).is_err());
b.write_all(b"r").unwrap();
let read = fd.async_io(Interest::READABLE, |mut a| a.read(&mut buf));
assert_eq!(read.await.unwrap(), 1);
let mut first = fd.readable().await.unwrap();
let err = fd.get_ref().read(&mut buf).unwrap_err();
assert_eq!(err.kind(), io::ErrorKind::WouldBlock);
b.write_all(b"x").unwrap();
if on_ring() {
first.clear_ready();
let n = fd.async_io(Interest::READABLE, |mut a| a.read(&mut buf));
assert_eq!(n.await.unwrap(), 1);
return;
}
let deadline = Instant::now() + Duration::from_secs(10);
loop {
crate::task::yield_now().await;
let look = fd.readable().await.unwrap();
if look.marks.read != first.marks.read {
break;
}
assert!(Instant::now() < deadline, "the driver never saw the edge");
}
first.clear_ready();
let mut cx = Context::from_waker(std::task::Waker::noop());
assert!(
fd.poll_read_ready(&mut cx).is_ready(),
"the edge after the call was lost to the clear"
);
assert_eq!(fd.get_ref().read(&mut buf).unwrap(), 1);
});
}
fn fill(fd: &AsyncFd<UnixStream>) -> usize {
let chunk = [5u8; 4096];
let mut sent = 0;
loop {
match fd.get_ref().write(&chunk) {
Ok(n) => sent += n,
Err(err) if err.kind() == io::ErrorKind::WouldBlock => return sent,
Err(err) => panic!("{err}"),
}
}
}
#[test]
fn a_write_edge_after_the_call_survives_the_clear() {
crate::block_on(async {
let (a, mut b) = UnixStream::pair().unwrap();
a.set_nonblocking(true).unwrap();
let fd = AsyncFd::with_interest(a, Interest::WRITABLE).unwrap();
let mut sink = vec![0u8; 1 << 20];
let mut guard = fd.writable().await.unwrap();
let sent = fill(&fd);
guard.clear_ready();
b.read_exact(&mut sink[..sent]).unwrap();
fd.writable().await.unwrap();
let mut first = fd.writable().await.unwrap();
let sent = fill(&fd);
b.read_exact(&mut sink[..sent]).unwrap();
if on_ring() {
first.clear_ready();
let n = fd.async_io(Interest::WRITABLE, |mut a| a.write(b"w"));
assert_eq!(n.await.unwrap(), 1);
return;
}
let deadline = Instant::now() + Duration::from_secs(10);
loop {
crate::task::yield_now().await;
let look = fd.writable().await.unwrap();
if look.marks.write != first.marks.write {
break;
}
assert!(Instant::now() < deadline, "the driver never saw the edge");
}
first.clear_ready();
let mut cx = Context::from_waker(std::task::Waker::noop());
assert!(
fd.poll_write_ready(&mut cx).is_ready(),
"the edge after the call was lost to the clear"
);
});
}
}