#![cfg_attr(not(any(feature = "tokio-compat", feature = "http")), allow(dead_code))]
use std::{
alloc::{alloc, dealloc, handle_alloc_error, Layout},
future::Future,
io,
mem::MaybeUninit,
pin::Pin,
ptr::NonNull,
rc::Rc,
task::{Context, Poll},
};
const MIN_READ: usize = 16 * 1024;
const MAX_READ: usize = 256 * 1024;
const WRITE_CHUNK: usize = 256 * 1024;
type ShutdownFut = Pin<Box<dyn Future<Output = io::Result<()>>>>;
struct ReusableLocalBoxFuture<T> {
ptr: NonNull<u8>,
layout: Layout,
occupied: bool,
poll_fn: unsafe fn(NonNull<u8>, &mut Context<'_>) -> Poll<T>,
drop_fn: unsafe fn(NonNull<u8>),
}
impl<T> ReusableLocalBoxFuture<T> {
fn empty() -> Self {
unsafe fn noop_poll<T>(_: NonNull<u8>, _: &mut Context<'_>) -> Poll<T> {
unreachable!("polled empty ReusableLocalBoxFuture")
}
unsafe fn noop_drop(_: NonNull<u8>) {}
Self {
ptr: NonNull::dangling(),
layout: Layout::new::<()>(),
occupied: false,
poll_fn: noop_poll::<T>,
drop_fn: noop_drop,
}
}
fn set<F: Future<Output = T> + 'static>(&mut self, fut: F) {
if self.occupied {
self.occupied = false;
unsafe { (self.drop_fn)(self.ptr) };
}
let new_layout = Layout::new::<F>();
if new_layout.size() > 0
&& (new_layout.size() > self.layout.size() || new_layout.align() > self.layout.align())
{
if self.layout.size() > 0 {
unsafe { dealloc(self.ptr.as_ptr(), self.layout) };
self.layout = Layout::new::<()>();
self.ptr = NonNull::dangling();
}
let raw = unsafe { alloc(new_layout) };
let Some(nn) = NonNull::new(raw) else {
handle_alloc_error(new_layout);
};
self.ptr = nn;
self.layout = new_layout;
}
if new_layout.size() > 0 {
unsafe { self.ptr.as_ptr().cast::<F>().write(fut) };
} else {
std::mem::forget(fut);
}
unsafe fn poll_impl<F: Future<Output = T>, T>(
ptr: NonNull<u8>,
cx: &mut Context<'_>,
) -> Poll<T> {
let pin = unsafe { Pin::new_unchecked(&mut *ptr.as_ptr().cast::<F>()) };
pin.poll(cx)
}
unsafe fn drop_impl<F>(ptr: NonNull<u8>) {
unsafe { std::ptr::drop_in_place(ptr.as_ptr().cast::<F>()) };
}
self.poll_fn = poll_impl::<F, T>;
self.drop_fn = drop_impl::<F>;
self.occupied = true;
}
#[inline]
fn poll(&mut self, cx: &mut Context<'_>) -> Poll<T> {
debug_assert!(self.occupied);
unsafe { (self.poll_fn)(self.ptr, cx) }
}
}
impl<T> Drop for ReusableLocalBoxFuture<T> {
fn drop(&mut self) {
if self.occupied {
self.occupied = false;
unsafe { (self.drop_fn)(self.ptr) };
}
if self.layout.size() > 0 {
unsafe { dealloc(self.ptr.as_ptr(), self.layout) };
}
}
}
pub trait Duplex: sealed::Sealed + Sized + 'static {
#[doc(hidden)]
fn read_owned(
self: Rc<Self>,
buf: Vec<u8>,
) -> impl Future<Output = (io::Result<usize>, Vec<u8>)> + 'static;
#[doc(hidden)]
fn write_all_owned(
self: Rc<Self>,
buf: Vec<u8>,
) -> impl Future<Output = (io::Result<usize>, Vec<u8>)> + 'static;
#[doc(hidden)]
fn shutdown_owned(self: Rc<Self>) -> ShutdownFut;
}
pub(crate) mod sealed {
pub trait Sealed {}
}
macro_rules! impl_duplex {
($ty:ty) => {
impl sealed::Sealed for $ty {}
impl Duplex for $ty {
fn read_owned(
self: Rc<Self>,
buf: Vec<u8>,
) -> impl Future<Output = (io::Result<usize>, Vec<u8>)> + 'static {
async move { self.read(buf).await }
}
fn write_all_owned(
self: Rc<Self>,
buf: Vec<u8>,
) -> impl Future<Output = (io::Result<usize>, Vec<u8>)> + 'static {
async move {
let len = buf.len();
let (res, buf) = self.write_all(buf).await;
(res.map(|()| len), buf)
}
}
fn shutdown_owned(self: Rc<Self>) -> ShutdownFut {
Box::pin(async move { self.shutdown(std::net::Shutdown::Write) })
}
}
};
}
impl_duplex!(crate::net::TcpStream);
#[cfg(unix)]
impl_duplex!(crate::net::UnixStream);
enum ReadState {
Idle(Option<Vec<u8>>),
Pending,
Buffered {
buf: Vec<u8>,
pos: usize,
len: usize,
},
}
enum WriteState {
Idle(Option<Vec<u8>>),
Pending,
}
pub struct Compat<S: Duplex> {
stream: Rc<S>,
read: ReadState,
read_fut: ReusableLocalBoxFuture<(io::Result<usize>, Vec<u8>)>,
write: WriteState,
write_fut: ReusableLocalBoxFuture<(io::Result<usize>, Vec<u8>)>,
write_err: Option<io::Error>,
shutdown: ShutdownState,
read_hint: usize,
small_reads: u8,
}
enum ShutdownState {
NotStarted,
Pending(ShutdownFut),
Done,
}
impl<S: Duplex> Compat<S> {
pub fn new(stream: S) -> Self {
Self {
stream: Rc::new(stream),
read: ReadState::Idle(None),
read_fut: ReusableLocalBoxFuture::empty(),
write: WriteState::Idle(None),
write_fut: ReusableLocalBoxFuture::empty(),
write_err: None,
shutdown: ShutdownState::NotStarted,
read_hint: MIN_READ,
small_reads: 0,
}
}
pub fn get_ref(&self) -> &S {
&self.stream
}
pub(crate) fn poll_read_uninit(
&mut self,
cx: &mut Context<'_>,
dst: &mut [MaybeUninit<u8>],
) -> Poll<io::Result<usize>> {
if dst.is_empty() {
return Poll::Ready(Ok(0));
}
loop {
match &mut self.read {
ReadState::Buffered { buf, pos, len } => {
let n = dst.len().min(*len - *pos);
unsafe {
std::ptr::copy_nonoverlapping(
buf.as_ptr().add(*pos),
dst.as_mut_ptr().cast::<u8>(),
n,
);
}
*pos += n;
if *pos == *len {
let spare = std::mem::take(buf);
self.read = ReadState::Idle(Some(spare));
}
return Poll::Ready(Ok(n));
}
ReadState::Idle(spare) => {
let mut buf = spare.take().unwrap_or_default();
buf.clear();
let want = self.read_hint;
if buf.capacity() > want * 2 {
buf = Vec::with_capacity(want);
} else {
buf.reserve(want);
}
self.read_fut.set(self.stream.clone().read_owned(buf));
self.read = ReadState::Pending;
}
ReadState::Pending => match self.read_fut.poll(cx) {
Poll::Pending => return Poll::Pending,
Poll::Ready((res, buf)) => match res {
Ok(0) => {
self.read = ReadState::Idle(Some(buf));
return Poll::Ready(Ok(0));
}
Ok(len) => {
if len * 10 >= buf.capacity() * 9 {
self.read_hint = (self.read_hint * 2).min(MAX_READ);
self.small_reads = 0;
} else if len * 8 < self.read_hint {
self.small_reads += 1;
if self.small_reads >= 32 {
self.read_hint = (self.read_hint / 2).max(MIN_READ);
self.small_reads = 0;
}
} else {
self.small_reads = 0;
}
self.read = ReadState::Buffered { buf, pos: 0, len }
}
Err(e) => {
self.read = ReadState::Idle(Some(buf));
return Poll::Ready(Err(e));
}
},
},
}
}
}
fn poll_drain(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
if let Some(e) = self.write_err.take() {
return Poll::Ready(Err(e));
}
if let WriteState::Pending = &self.write {
match self.write_fut.poll(cx) {
Poll::Pending => return Poll::Pending,
Poll::Ready((res, buf)) => {
self.write = WriteState::Idle(Some(buf));
res?;
}
}
}
Poll::Ready(Ok(()))
}
pub(crate) fn poll_write_bytes(
&mut self,
cx: &mut Context<'_>,
src: &[u8],
) -> Poll<io::Result<usize>> {
if src.is_empty() {
return Poll::Ready(Ok(0));
}
match self.poll_drain(cx) {
Poll::Pending => return Poll::Pending,
Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
Poll::Ready(Ok(())) => {}
}
let WriteState::Idle(spare) = &mut self.write else {
unreachable!("poll_drain leaves the write state idle");
};
let mut buf = spare.take().unwrap_or_default();
buf.clear();
let n = src.len().min(WRITE_CHUNK);
buf.extend_from_slice(&src[..n]);
self.write_fut.set(self.stream.clone().write_all_owned(buf));
match self.write_fut.poll(cx) {
Poll::Pending => self.write = WriteState::Pending,
Poll::Ready((res, buf)) => {
self.write = WriteState::Idle(Some(buf));
if let Err(e) = res {
self.write_err = Some(e);
}
}
}
Poll::Ready(Ok(n))
}
pub(crate) fn poll_flush_all(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
self.poll_drain(cx)
}
pub(crate) fn poll_shutdown_write(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
match self.poll_drain(cx) {
Poll::Pending => return Poll::Pending,
Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
Poll::Ready(Ok(())) => {}
}
loop {
match &mut self.shutdown {
ShutdownState::Done => return Poll::Ready(Ok(())),
ShutdownState::NotStarted => {
self.shutdown = ShutdownState::Pending(self.stream.clone().shutdown_owned());
}
ShutdownState::Pending(fut) => match fut.as_mut().poll(cx) {
Poll::Pending => return Poll::Pending,
Poll::Ready(res) => {
self.shutdown = ShutdownState::Done;
return Poll::Ready(res);
}
},
}
}
}
}
#[cfg(feature = "tokio-compat")]
mod tokio_impl {
use super::*;
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
impl<S: Duplex> AsyncRead for Compat<S> {
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
let this = self.get_mut();
let dst = unsafe { buf.unfilled_mut() };
let n = match this.poll_read_uninit(cx, dst) {
Poll::Pending => return Poll::Pending,
Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
Poll::Ready(Ok(n)) => n,
};
unsafe { buf.assume_init(n) };
buf.advance(n);
Poll::Ready(Ok(()))
}
}
impl<S: Duplex> AsyncWrite for Compat<S> {
fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
self.get_mut().poll_write_bytes(cx, buf)
}
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
self.get_mut().poll_flush_all(cx)
}
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
self.get_mut().poll_shutdown_write(cx)
}
}
}
#[cfg(feature = "http")]
mod hyper_impl {
use super::*;
impl<S: Duplex> hyper::rt::Read for Compat<S> {
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
mut buf: hyper::rt::ReadBufCursor<'_>,
) -> Poll<io::Result<()>> {
let this = self.get_mut();
let dst = unsafe { buf.as_mut() };
let n = match this.poll_read_uninit(cx, dst) {
Poll::Pending => return Poll::Pending,
Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
Poll::Ready(Ok(n)) => n,
};
unsafe { buf.advance(n) };
Poll::Ready(Ok(()))
}
}
impl<S: Duplex> hyper::rt::Write for Compat<S> {
fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
self.get_mut().poll_write_bytes(cx, buf)
}
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
self.get_mut().poll_flush_all(cx)
}
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
self.get_mut().poll_shutdown_write(cx)
}
}
}