use crate::{Buffer, Conn, ProtocolSession, Version};
use futures_lite::{AsyncRead, AsyncWrite};
use std::{
future::Future,
pin::Pin,
task::{Context, Poll},
};
const PROBE_WINDOW_CAP: usize = 16 * 1024;
pub type PeerGone = Pin<Box<dyn Future<Output = ()> + Send + Sync + 'static>>;
pub(crate) fn poll_peer_gone<T: AsyncRead + Unpin>(
session: &ProtocolSession,
version: Version,
buffer: &mut Buffer,
transport: &mut T,
peer_gone: Option<&mut PeerGone>,
read_allowance: usize,
cx: &mut Context<'_>,
) -> Poll<()> {
match session {
ProtocolSession::Http2 {
connection,
stream_id,
} => return connection.poll_stream_closed(*stream_id, cx),
ProtocolSession::Http3 { .. } => {
return match peer_gone {
Some(peer_gone) => peer_gone.as_mut().poll(cx),
None => Poll::Pending,
};
}
ProtocolSession::Http1 => {}
}
if !matches!(
version,
Version::Http0_9 | Version::Http1_0 | Version::Http1_1
) {
return Poll::Pending;
}
loop {
let want = read_allowance.saturating_sub(buffer.live_len());
if want == 0 {
return Poll::Pending;
}
match Pin::new(&mut *transport).poll_read(cx, buffer.window(want)) {
Poll::Ready(Ok(0) | Err(_)) => return Poll::Ready(()),
Poll::Ready(Ok(n)) => buffer.advance(n),
Poll::Pending => return Poll::Pending,
}
}
}
pub(crate) struct LivenessFut<'a, T>(&'a mut Conn<T>);
impl<'a, T> LivenessFut<'a, T> {
pub(crate) fn new(conn: &'a mut Conn<T>) -> Self {
Self(conn)
}
}
impl<T> Future for LivenessFut<'_, T>
where
T: AsyncRead + AsyncWrite + Send + Sync + Unpin + 'static,
{
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let LivenessFut(Conn {
buffer,
transport,
protocol_session,
version,
peer_gone,
..
}) = &mut *self;
poll_peer_gone(
protocol_session,
*version,
buffer,
transport,
peer_gone.as_mut(),
PROBE_WINDOW_CAP,
cx,
)
}
}
pub(crate) struct CancelOnDisconnect<'a, Fut, T>(
pub(crate) &'a mut Conn<T>,
pub(crate) Pin<&'a mut Fut>,
);
impl<'a, Fut, T> Future for CancelOnDisconnect<'a, Fut, T>
where
Fut: Future + Send + 'a,
T: AsyncRead + AsyncWrite + Send + Sync + Unpin + 'static,
{
type Output = Option<Fut::Output>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let CancelOnDisconnect(conn, fut) = &mut *self;
let fut_poll = fut.as_mut().poll(cx);
let disconnect = Pin::new(&mut LivenessFut(conn)).poll(cx);
match (fut_poll, disconnect) {
(Poll::Ready(output), _) => Poll::Ready(Some(output)),
(Poll::Pending, Poll::Ready(())) => Poll::Ready(None),
(Poll::Pending, Poll::Pending) => Poll::Pending,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{HttpContext, ProtocolSession, h3::H3Connection};
use futures_lite::io::Cursor;
use std::sync::{
Arc,
atomic::{AtomicBool, Ordering},
};
fn gated(flag: Arc<AtomicBool>) -> PeerGone {
Box::pin(std::future::poll_fn(move |cx| {
if flag.load(Ordering::SeqCst) {
Poll::Ready(())
} else {
cx.waker().wake_by_ref();
Poll::Pending
}
}))
}
fn h3_session() -> ProtocolSession {
ProtocolSession::Http3 {
connection: H3Connection::new(Arc::new(HttpContext::new())),
stream_id: 0,
}
}
#[test]
fn h3_ignores_an_eof_transport() {
let mut transport = Cursor::new(Vec::new());
let mut buffer = Buffer::default();
let polled = futures_lite::future::block_on(std::future::poll_fn(|cx| {
Poll::Ready(poll_peer_gone(
&h3_session(),
Version::Http3,
&mut buffer,
&mut transport,
None,
16 * 1024,
cx,
))
}));
assert!(
polled.is_pending(),
"an h3 client that finished sending its request has not departed"
);
}
#[test]
fn h3_reports_departure_only_once_signalled() {
let flag = Arc::new(AtomicBool::new(false));
let mut peer_gone = gated(flag.clone());
let mut transport = Cursor::new(Vec::new());
let mut buffer = Buffer::default();
let session = h3_session();
let poll =
|peer_gone: &mut PeerGone, transport: &mut Cursor<Vec<u8>>, buffer: &mut Buffer| {
futures_lite::future::block_on(std::future::poll_fn(|cx| {
Poll::Ready(poll_peer_gone(
&session,
Version::Http3,
buffer,
transport,
Some(peer_gone),
16 * 1024,
cx,
))
}))
};
assert!(poll(&mut peer_gone, &mut transport, &mut buffer).is_pending());
flag.store(true, Ordering::SeqCst);
assert!(
poll(&mut peer_gone, &mut transport, &mut buffer).is_ready(),
"once the adapter signals abandonment, the probe must resolve"
);
}
}