use super::{Phase, PhaseClock};
use bytes::Bytes;
use std::cell::RefCell;
use std::io;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
const MAX_SCRATCH: usize = 64 * 1024;
pub(crate) struct IoShared<IO> {
pub(crate) io: IO,
pub(crate) buffered: Bytes,
pub(crate) clock: Rc<PhaseClock>,
scratch: Vec<u8>,
}
impl<IO: AsyncRead + AsyncWrite + Unpin> IoShared<IO> {
fn poll_read_into(
&mut self,
cx: &mut Context<'_>,
out: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
if !self.buffered.is_empty() {
let n = self.buffered.len().min(out.remaining());
if n > 0 {
out.put_slice(&self.buffered.split_to(n));
self.note_read();
}
return Poll::Ready(Ok(()));
}
let before = out.filled().len();
let poll = Pin::new(&mut self.io).poll_read(cx, out);
if matches!(poll, Poll::Ready(Ok(()))) && out.filled().len() > before {
self.note_read();
}
poll
}
fn note_read(&self) {
if self.clock.phase() == Phase::Idle {
self.clock.set(Phase::Head);
}
}
fn note_write(&self, n: usize) {
if n > 0 {
self.clock.note_write();
}
}
fn poll_write_from(&mut self, cx: &mut Context<'_>, buf: &[u8]) -> Poll<io::Result<usize>> {
let poll = Pin::new(&mut self.io).poll_write(cx, buf);
if let Poll::Ready(Ok(n)) = &poll {
self.note_write(*n);
}
poll
}
fn poll_flush_from(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
let poll = Pin::new(&mut self.io).poll_flush(cx);
if matches!(poll, Poll::Ready(Ok(()))) {
self.clock.note_flush();
}
poll
}
fn poll_read_cursor(
&mut self,
cx: &mut Context<'_>,
mut buf: ::hyper::rt::ReadBufCursor<'_>,
) -> Poll<io::Result<()>> {
let want = buf.remaining().min(MAX_SCRATCH);
debug_assert!(
want > 0,
"hyper::rt::Read was polled with a zero-remaining cursor; \
a zero-byte read would be reported to hyper as end-of-stream"
);
if want == 0 {
cx.waker().wake_by_ref();
return Poll::Pending;
}
let mut scratch = std::mem::take(&mut self.scratch);
if scratch.len() < want {
scratch.resize(want, 0);
}
let mut read_buf = ReadBuf::new(&mut scratch[..want]);
let result = match self.poll_read_into(cx, &mut read_buf) {
Poll::Pending => Poll::Pending,
Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
Poll::Ready(Ok(())) => {
buf.put_slice(read_buf.filled());
Poll::Ready(Ok(()))
}
};
self.scratch = scratch;
result
}
}
pub(crate) struct HyperIo<IO>(pub(crate) Rc<RefCell<IoShared<IO>>>);
impl<IO: AsyncRead + AsyncWrite + Unpin> HyperIo<IO> {
pub(crate) fn new(
io: IO,
buffered: Bytes,
clock: Rc<PhaseClock>,
) -> (Self, Rc<RefCell<IoShared<IO>>>) {
let shared = Rc::new(RefCell::new(IoShared {
io,
buffered,
clock,
scratch: Vec::new(),
}));
(Self(shared.clone()), shared)
}
}
impl<IO: AsyncRead + AsyncWrite + Unpin> ::hyper::rt::Read for HyperIo<IO> {
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: ::hyper::rt::ReadBufCursor<'_>,
) -> Poll<io::Result<()>> {
self.0.borrow_mut().poll_read_cursor(cx, buf)
}
}
impl<IO: AsyncRead + AsyncWrite + Unpin> ::hyper::rt::Write for HyperIo<IO> {
fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
self.0.borrow_mut().poll_write_from(cx, buf)
}
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
self.0.borrow_mut().poll_flush_from(cx)
}
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Pin::new(&mut self.0.borrow_mut().io).poll_shutdown(cx)
}
}
pub(crate) struct SharedIo<IO>(pub(crate) Rc<RefCell<IoShared<IO>>>);
impl<IO: AsyncRead + AsyncWrite + Unpin> AsyncRead for SharedIo<IO> {
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
self.0.borrow_mut().poll_read_into(cx, buf)
}
}
impl<IO: AsyncRead + AsyncWrite + Unpin> AsyncWrite for SharedIo<IO> {
fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
self.0.borrow_mut().poll_write_from(cx, buf)
}
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
self.0.borrow_mut().poll_flush_from(cx)
}
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Pin::new(&mut self.0.borrow_mut().io).poll_shutdown(cx)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::pin::Pin;
use std::rc::Rc;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
fn clock() -> Rc<PhaseClock> {
Rc::new(PhaseClock::new(Phase::Idle))
}
#[tokio::test]
async fn write_passes_through_and_flushes() {
let (mut client, server) = tokio::io::duplex(4096);
let (io, _shared) = HyperIo::new(server, bytes::Bytes::new(), clock());
let mut io = io;
std::future::poll_fn(|cx| ::hyper::rt::Write::poll_write(Pin::new(&mut io), cx, b"abc"))
.await
.unwrap();
std::future::poll_fn(|cx| ::hyper::rt::Write::poll_flush(Pin::new(&mut io), cx))
.await
.unwrap();
let mut buf = [0u8; 3];
client.read_exact(&mut buf).await.unwrap();
assert_eq!(&buf, b"abc");
}
#[tokio::test]
async fn shared_io_reads_and_writes_through_the_same_state() {
let (mut client, server) = tokio::io::duplex(4096);
let (_hyper_io, shared) = HyperIo::new(server, bytes::Bytes::from_static(b"pre"), clock());
let mut sio = SharedIo(shared);
let mut buf = [0u8; 3];
sio.read_exact(&mut buf).await.unwrap();
assert_eq!(&buf, b"pre");
client.write_all(b"xyz").await.unwrap();
sio.read_exact(&mut buf).await.unwrap();
assert_eq!(&buf, b"xyz");
sio.write_all(b"ok").await.unwrap();
sio.flush().await.unwrap();
let mut out = [0u8; 2];
client.read_exact(&mut out).await.unwrap();
assert_eq!(&out, b"ok");
}
#[tokio::test]
async fn first_byte_moves_the_clock_from_idle_to_head() {
let (mut client, server) = tokio::io::duplex(4096);
let c = clock();
let (_hyper_io, shared) = HyperIo::new(server, bytes::Bytes::new(), c.clone());
let mut sio = SharedIo(shared);
assert_eq!(c.phase(), Phase::Idle);
client.write_all(b"G").await.unwrap();
let mut buf = [0u8; 1];
sio.read_exact(&mut buf).await.unwrap();
assert_eq!(c.phase(), Phase::Head);
}
#[tokio::test]
async fn reads_during_a_write_do_not_re_phase() {
let (mut client, server) = tokio::io::duplex(4096);
let c = clock();
let (_hyper_io, shared) = HyperIo::new(server, bytes::Bytes::new(), c.clone());
let mut sio = SharedIo(shared);
c.set(Phase::Write);
client.write_all(b"G").await.unwrap();
let mut buf = [0u8; 1];
sio.read_exact(&mut buf).await.unwrap();
assert_eq!(c.phase(), Phase::Write);
}
#[tokio::test]
async fn writes_report_progress_to_the_clock() {
let (mut client, server) = tokio::io::duplex(4096);
let c = clock();
let (_hyper_io, shared) = HyperIo::new(server, bytes::Bytes::new(), c.clone());
let mut sio = SharedIo(shared);
c.set(Phase::Write);
assert!(!c.wrote_bytes());
let before = c.generation.get();
sio.write_all(b"ok").await.unwrap();
assert!(c.wrote_bytes(), "a nonzero write starts the response");
assert!(c.generation.get() > before, "write progress re-arms");
let mut out = [0u8; 2];
client.read_exact(&mut out).await.unwrap();
c.set(Phase::Handler);
assert!(!c.wrote_bytes());
}
}