use crate::CoreError;
use crate::fd::Fd;
use std::sync::Arc;
const READ_CHUNK: usize = 65536;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SinkResult {
Accept,
Pause,
}
pub type ChunkSink = Arc<dyn Fn(bool, &[u8]) -> SinkResult + Send + Sync>;
#[derive(Default)]
#[repr(align(64))]
pub(crate) struct BufferState {
stdout: Vec<u8>,
stderr: Vec<u8>,
limit: usize,
output_limit_exceeded: bool,
stdout_early_exited: bool,
sink: Option<ChunkSink>,
scratch: Vec<u8>,
stdout_pending: Option<Vec<u8>>,
stderr_pending: Option<Vec<u8>>,
stdout_paused: bool,
stderr_paused: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ReadState {
Open,
Eof,
EarlyExit,
Paused,
}
impl BufferState {
pub(crate) fn new(limit: usize, sink: Option<ChunkSink>) -> Self {
Self {
stdout: Vec::with_capacity(1024),
stderr: Vec::with_capacity(1024),
limit,
output_limit_exceeded: false,
stdout_early_exited: false,
sink,
scratch: Vec::with_capacity(READ_CHUNK),
stdout_pending: None,
stderr_pending: None,
stdout_paused: false,
stderr_paused: false,
}
}
#[inline(always)]
pub(crate) fn read_from_fd(
&mut self,
fd: &Fd,
is_stdout: bool,
early_exit: &mut Option<impl FnMut(&[u8]) -> bool>,
) -> Result<ReadState, CoreError> {
if self.sink.is_some() {
return self.read_from_fd_streaming(fd, is_stdout, early_exit);
}
loop {
let current_total = self.stdout.len().saturating_add(self.stderr.len());
let remaining_limit = self.limit.saturating_sub(current_total);
if remaining_limit == 0 {
let mut drop_buf = [0u8; 8192];
match fd.read_slice(&mut drop_buf) {
Ok(Some(n)) if n > 0 => {
self.output_limit_exceeded = true;
continue;
}
Ok(Some(_)) => return Ok(ReadState::Eof),
Ok(None) => return Ok(ReadState::Open),
Err(e) => return Err(e),
}
}
let dest = if is_stdout {
&mut self.stdout
} else {
&mut self.stderr
};
let len = dest.len();
let to_read = remaining_limit.min(READ_CHUNK);
dest.resize(len + to_read, 0);
match fd.read_slice(&mut dest[len..len + to_read]) {
Ok(Some(n)) if n > 0 => {
dest.truncate(len + n);
if is_stdout
&& let Some(f) = early_exit
&& f(&dest[len..len + n])
{
self.stdout_early_exited = true;
return Ok(ReadState::EarlyExit);
}
}
Ok(Some(_)) => {
dest.truncate(len);
return Ok(ReadState::Eof);
}
Ok(None) => {
dest.truncate(len);
return Ok(ReadState::Open);
}
Err(e) => {
dest.truncate(len);
return Err(e);
}
}
}
}
fn read_from_fd_streaming(
&mut self,
fd: &Fd,
is_stdout: bool,
early_exit: &mut Option<impl FnMut(&[u8]) -> bool>,
) -> Result<ReadState, CoreError> {
let Some(sink) = &self.sink else {
return Ok(ReadState::Open);
};
loop {
let pending = if is_stdout {
self.stdout_pending.take()
} else {
self.stderr_pending.take()
};
if let Some(pending) = pending {
match sink(is_stdout, &pending) {
SinkResult::Accept => {
if is_stdout {
self.stdout_paused = false;
} else {
self.stderr_paused = false;
}
}
SinkResult::Pause => {
if is_stdout {
self.stdout_pending = Some(pending);
self.stdout_paused = true;
} else {
self.stderr_pending = Some(pending);
self.stderr_paused = true;
}
return Ok(ReadState::Paused);
}
}
}
self.scratch.resize(READ_CHUNK, 0);
match fd.read_slice(&mut self.scratch) {
Ok(Some(n)) if n > 0 => {
self.scratch.truncate(n);
if is_stdout
&& let Some(f) = early_exit
&& f(&self.scratch)
{
self.stdout_early_exited = true;
return Ok(ReadState::EarlyExit);
}
match sink(is_stdout, &self.scratch) {
SinkResult::Accept => continue,
SinkResult::Pause => {
if is_stdout {
self.stdout_pending = Some(self.scratch.clone());
self.stdout_paused = true;
} else {
self.stderr_pending = Some(self.scratch.clone());
self.stderr_paused = true;
}
return Ok(ReadState::Paused);
}
}
}
Ok(Some(_)) => {
self.scratch.clear();
return Ok(ReadState::Eof);
}
Ok(None) => {
self.scratch.clear();
return Ok(ReadState::Open);
}
Err(e) => {
self.scratch.clear();
return Err(e);
}
}
}
}
pub(crate) fn deliver_pending_stdout(&mut self) -> Result<bool, CoreError> {
if !self.stdout_paused {
return Ok(true);
}
let Some(sink) = &self.sink else {
self.stdout_paused = false;
self.stdout_pending = None;
return Ok(true);
};
match self.stdout_pending.take() {
None => {
self.stdout_paused = false;
Ok(true)
}
Some(pending) => match sink(true, &pending) {
SinkResult::Accept => {
self.stdout_paused = false;
Ok(true)
}
SinkResult::Pause => {
self.stdout_pending = Some(pending);
Ok(false)
}
},
}
}
pub(crate) fn deliver_pending_stderr(&mut self) -> Result<bool, CoreError> {
if !self.stderr_paused {
return Ok(true);
}
let Some(sink) = &self.sink else {
self.stderr_paused = false;
self.stderr_pending = None;
return Ok(true);
};
match self.stderr_pending.take() {
None => {
self.stderr_paused = false;
Ok(true)
}
Some(pending) => match sink(false, &pending) {
SinkResult::Accept => {
self.stderr_paused = false;
Ok(true)
}
SinkResult::Pause => {
self.stderr_pending = Some(pending);
Ok(false)
}
},
}
}
#[inline(always)]
pub(crate) fn stdout_paused(&self) -> bool {
self.stdout_paused
}
#[inline(always)]
pub(crate) fn stderr_paused(&self) -> bool {
self.stderr_paused
}
pub(crate) fn take_stdout_pending(&mut self) -> Option<Vec<u8>> {
self.stdout_pending.take()
}
pub(crate) fn take_stderr_pending(&mut self) -> Option<Vec<u8>> {
self.stderr_pending.take()
}
#[inline(always)]
pub(crate) fn output_limit_exceeded(&self) -> bool {
self.output_limit_exceeded
}
#[inline(always)]
pub(crate) fn stdout_early_exited(&self) -> bool {
self.stdout_early_exited
}
pub(crate) fn into_parts(mut self) -> (Vec<u8>, Vec<u8>, bool, bool) {
(
std::mem::take(&mut self.stdout),
std::mem::take(&mut self.stderr),
self.output_limit_exceeded,
self.stdout_early_exited,
)
}
}