use std::future::poll_fn;
use std::sync::{Arc, Mutex, MutexGuard};
use std::task::{Context, Poll, Waker};
pub(crate) const PIPE_CAPACITY: usize = 4 * 1024;
#[derive(Default)]
struct State {
buf: Vec<u8>,
pos: usize,
write_closed: bool,
read_closed: bool,
broken: bool,
read_waker: Option<Waker>,
write_waker: Option<Waker>,
}
impl State {
fn available(&self) -> usize {
self.buf.len() - self.pos
}
fn wake_reader(&mut self) {
if let Some(w) = self.read_waker.take() {
w.wake();
}
}
fn wake_writer(&mut self) {
if let Some(w) = self.write_waker.take() {
w.wake();
}
}
fn take(&mut self, n: usize) -> Vec<u8> {
let end = self.pos + n.min(self.available());
let out = self.buf[self.pos..end].to_vec();
self.pos = end;
if self.pos == self.buf.len() {
self.buf.clear();
self.pos = 0;
}
self.wake_writer();
out
}
}
#[derive(Default)]
pub(crate) struct Pipe {
state: Mutex<State>,
_metadata_storage: Option<crate::limits::ExecutionBudgetLease>,
}
impl Pipe {
pub(crate) fn new() -> Arc<Self> {
Arc::new(Self::default())
}
pub(crate) fn new_admitted(
budget: &crate::limits::ExecutionBudget,
) -> crate::error::Result<Arc<Self>> {
let storage = budget.lease_bytes(
std::mem::size_of::<Self>() + 2 * std::mem::size_of::<usize>() + PIPE_CAPACITY,
)?;
Ok(Arc::new(Self {
state: Mutex::new(State {
buf: Vec::with_capacity(PIPE_CAPACITY),
..Default::default()
}),
_metadata_storage: Some(storage),
}))
}
fn lock(&self) -> MutexGuard<'_, State> {
self.state.lock().unwrap_or_else(|e| e.into_inner())
}
pub(crate) fn write(&self, data: &[u8]) -> bool {
if data.is_empty() {
return true;
}
let mut s = self.lock();
if s.read_closed {
s.broken = true;
return false;
}
s.buf.extend_from_slice(data);
s.wake_reader();
true
}
pub(crate) async fn write_bounded(&self, mut data: &[u8]) -> std::io::Result<()> {
while !data.is_empty() {
let written = poll_fn(|cx| {
let mut s = self.lock();
if s.read_closed {
s.broken = true;
return Poll::Ready(Err(std::io::Error::from(std::io::ErrorKind::BrokenPipe)));
}
let room = PIPE_CAPACITY.saturating_sub(s.available());
if room == 0 {
s.write_waker = Some(cx.waker().clone());
return Poll::Pending;
}
if s.pos > 0 {
let pos = s.pos;
s.buf.drain(..pos);
s.pos = 0;
}
let n = data.len().min(room);
s.buf.extend_from_slice(&data[..n]);
s.wake_reader();
Poll::Ready(Ok(n))
})
.await?;
data = &data[written..];
}
Ok(())
}
pub(crate) fn is_broken(&self) -> bool {
self.lock().broken
}
pub(crate) async fn writable(&self) {
poll_fn(|cx: &mut Context<'_>| {
let mut s = self.lock();
if s.read_closed || s.available() < PIPE_CAPACITY {
Poll::Ready(())
} else {
s.write_waker = Some(cx.waker().clone());
Poll::Pending
}
})
.await
}
pub(crate) fn close_write(&self) {
let mut s = self.lock();
s.write_closed = true;
s.wake_reader();
}
pub(crate) fn close_read(&self) {
let mut s = self.lock();
s.read_closed = true;
s.buf.clear();
s.pos = 0;
s.wake_writer();
}
pub(crate) async fn read_some(&self) -> Vec<u8> {
self.read_chunk(false).await
}
pub(crate) async fn read_line_chunk(&self) -> Vec<u8> {
self.read_chunk(true).await
}
async fn read_chunk(&self, line: bool) -> Vec<u8> {
poll_fn(|cx: &mut Context<'_>| {
let mut s = self.lock();
let mut n = s.available();
if line && let Some(i) = s.buf[s.pos..].iter().position(|&b| b == b'\n') {
n = i + 1;
}
if n > 0 {
Poll::Ready(s.take(n))
} else if s.write_closed {
Poll::Ready(Vec::new())
} else {
s.read_waker = Some(cx.waker().clone());
Poll::Pending
}
})
.await
}
#[cfg(test)]
pub(crate) async fn read_to_end(&self) -> Vec<u8> {
let mut out = Vec::new();
loop {
let chunk = self.read_some().await;
if chunk.is_empty() {
return out;
}
out.extend_from_slice(&chunk);
}
}
}
pub(crate) struct WriteEnd(pub(crate) Arc<Pipe>);
impl Drop for WriteEnd {
fn drop(&mut self) {
self.0.close_write();
}
}
pub(crate) struct ReadEnd(pub(crate) Arc<Pipe>);
impl Drop for ReadEnd {
fn drop(&mut self) {
self.0.close_read();
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::future::Future;
#[tokio::test]
async fn live_writes_are_bounded_and_reader_close_wakes_writer() {
let pipe = Pipe::new();
let data = vec![0u8; PIPE_CAPACITY * 8];
let mut write = Box::pin(pipe.write_bounded(&data));
let waker = futures_util::task::noop_waker();
let mut cx = Context::from_waker(&waker);
assert!(write.as_mut().poll(&mut cx).is_pending());
assert_eq!(pipe.lock().available(), PIPE_CAPACITY);
assert_eq!(pipe.lock().buf.capacity(), PIPE_CAPACITY);
let _ = pipe.read_some().await;
assert!(write.as_mut().poll(&mut cx).is_pending());
assert_eq!(pipe.lock().available(), PIPE_CAPACITY);
drop(ReadEnd(pipe.clone()));
assert!(
matches!(write.as_mut().poll(&mut cx), Poll::Ready(Err(e)) if e.kind() == std::io::ErrorKind::BrokenPipe)
);
}
#[tokio::test]
async fn read_sees_writes_then_eof() {
let p = Pipe::new();
assert!(p.write(b"ab"));
assert!(p.write(b"c"));
p.close_write();
assert_eq!(p.read_to_end().await, b"abc");
assert!(p.read_some().await.is_empty());
}
#[tokio::test]
async fn line_reads_leave_the_rest() {
let p = Pipe::new();
p.write(b"a\nb\nc");
p.close_write();
assert_eq!(p.read_line_chunk().await, b"a\n");
assert_eq!(p.read_line_chunk().await, b"b\n");
assert_eq!(p.read_line_chunk().await, b"c");
assert!(p.read_line_chunk().await.is_empty());
}
#[tokio::test]
async fn write_after_reader_closed_breaks() {
let p = Pipe::new();
assert!(p.write(b"x"));
drop(ReadEnd(Arc::clone(&p)));
assert!(!p.is_broken());
assert!(!p.write(b"y"));
assert!(p.is_broken());
assert!(p.write(b""));
}
#[tokio::test]
async fn writer_waits_until_drained() {
let p = Pipe::new();
p.write(&vec![b'a'; PIPE_CAPACITY]);
let mut wait = Box::pin(p.writable());
let waker = futures_util::task::noop_waker();
let mut cx = Context::from_waker(&waker);
assert!(wait.as_mut().poll(&mut cx).is_pending());
assert_eq!(p.read_some().await.len(), PIPE_CAPACITY);
assert!(wait.as_mut().poll(&mut cx).is_ready());
}
}