use std::{
collections::VecDeque,
ffi::OsStr,
future::poll_fn,
io::{self, Read, Write},
path::Path,
process::Child as StdChild,
sync::{Arc, Condvar, Mutex},
task::{Context, Poll},
time::Duration,
};
pub use std::process::{ExitStatus, Output, Stdio};
use crate::{
blocking::{
current_mailbox, next_id, register_waker, run_detached, spawn_blocking, unregister_waker,
Mailbox,
},
buf::{BufResult, IoBuf, IoBufMut},
};
const MAX_BUFFERED: usize = 1 << 20;
const CHUNK: usize = 16 * 1024;
fn lock<T>(m: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
m.lock().unwrap_or_else(|e| e.into_inner())
}
struct InState {
chunks: VecDeque<Vec<u8>>,
front_off: usize,
buffered: usize,
eof: bool,
err: Option<io::Error>,
abandoned: bool,
}
struct PipeIn {
state: Mutex<InState>,
space: Condvar,
id: u64,
mailbox: Arc<Mailbox>,
}
impl PipeIn {
fn spawn(mut src: impl Read + Send + 'static) -> Arc<Self> {
let pipe = Arc::new(Self {
state: Mutex::new(InState {
chunks: VecDeque::new(),
front_off: 0,
buffered: 0,
eof: false,
err: None,
abandoned: false,
}),
space: Condvar::new(),
id: next_id(),
mailbox: current_mailbox(),
});
let p = pipe.clone();
let spawned = std::thread::Builder::new()
.name("nexio-pipe-in".into())
.spawn(move || {
let mut buf = vec![0u8; CHUNK];
loop {
{
let mut st = lock(&p.state);
while st.buffered >= MAX_BUFFERED && !st.abandoned {
st = p.space.wait(st).unwrap_or_else(|e| e.into_inner());
}
if st.abandoned {
return;
}
}
match src.read(&mut buf) {
Ok(0) => {
lock(&p.state).eof = true;
p.mailbox.complete(p.id);
return;
}
Ok(n) => {
let mut st = lock(&p.state);
st.chunks.push_back(buf[..n].to_vec());
st.buffered += n;
drop(st);
p.mailbox.complete(p.id);
}
Err(e) if e.kind() == io::ErrorKind::Interrupted => {}
Err(e) => {
let mut st = lock(&p.state);
st.err = Some(e);
st.eof = true;
drop(st);
p.mailbox.complete(p.id);
return;
}
}
}
});
if let Err(e) = spawned {
let mut st = lock(&pipe.state);
st.err = Some(e);
st.eof = true;
}
pipe
}
fn poll_read<B: IoBufMut>(&self, buf: &mut B, cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
let total = buf.bytes_total();
if total == 0 {
return Poll::Ready(Ok(0));
}
register_waker(self.id, cx.waker());
let mut st = lock(&self.state);
if let Some(front) = st.chunks.front() {
let off = st.front_off;
let n = (front.len() - off).min(total);
unsafe {
std::ptr::copy_nonoverlapping(front[off..].as_ptr(), buf.stable_ptr_mut(), n);
buf.set_init(n);
}
st.front_off += n;
st.buffered -= n;
if st.front_off == st.chunks.front().map_or(0, Vec::len) {
st.chunks.pop_front();
st.front_off = 0;
}
drop(st);
self.space.notify_one();
unregister_waker(self.id);
return Poll::Ready(Ok(n));
}
if let Some(e) = st.err.take() {
unregister_waker(self.id);
return Poll::Ready(Err(e));
}
if st.eof {
unregister_waker(self.id);
return Poll::Ready(Ok(0));
}
Poll::Pending
}
}
macro_rules! child_reader {
($(#[$doc:meta])* $name:ident) => {
$(#[$doc])*
pub struct $name {
pipe: Arc<PipeIn>,
}
impl $name {
pub async fn read<B>(&self, mut buf: B) -> BufResult<usize, B>
where
B: IoBufMut + Send + 'static,
{
let res = poll_fn(|cx| self.pipe.poll_read(&mut buf, cx)).await;
(res, buf)
}
pub async fn read_to_end(&self, out: &mut Vec<u8>) -> io::Result<usize> {
let mut total = 0;
let mut chunk = Vec::with_capacity(CHUNK);
loop {
let (res, mut returned) = self.read(chunk).await;
match res? {
0 => return Ok(total),
n => {
out.extend_from_slice(&returned[..n]);
total += n;
let cap = returned.capacity();
returned.clear();
if n * 10 >= cap * 9 && cap < (CHUNK * 16) {
returned.reserve((cap * 2).min(CHUNK * 16));
}
chunk = returned;
}
}
}
}
}
impl Drop for $name {
fn drop(&mut self) {
unregister_waker(self.pipe.id);
lock(&self.pipe.state).abandoned = true;
self.pipe.space.notify_all();
}
}
};
}
child_reader!(
ChildStdout
);
child_reader!(
ChildStderr
);
struct OutState {
chunks: VecDeque<Vec<u8>>,
buffered: usize,
writing: bool,
closing: bool,
closed: bool,
err: Option<(io::ErrorKind, String)>,
}
struct PipeOut {
state: Mutex<OutState>,
work: Condvar,
id: u64,
mailbox: Arc<Mailbox>,
}
impl PipeOut {
fn spawn(mut dst: impl Write + Send + 'static) -> Arc<Self> {
let pipe = Arc::new(Self {
state: Mutex::new(OutState {
chunks: VecDeque::new(),
buffered: 0,
writing: false,
closing: false,
closed: false,
err: None,
}),
work: Condvar::new(),
id: next_id(),
mailbox: current_mailbox(),
});
let p = pipe.clone();
let spawned = std::thread::Builder::new()
.name("nexio-pipe-out".into())
.spawn(move || loop {
let chunk = {
let mut st = lock(&p.state);
loop {
if let Some(c) = st.chunks.pop_front() {
st.writing = true;
break Some(c);
}
if st.closing {
break None;
}
st = p.work.wait(st).unwrap_or_else(|e| e.into_inner());
}
};
let Some(chunk) = chunk else {
drop(dst);
lock(&p.state).closed = true;
p.mailbox.complete(p.id);
return;
};
let res = dst.write_all(&chunk).and_then(|()| dst.flush());
let mut st = lock(&p.state);
st.writing = false;
st.buffered -= chunk.len();
if let Err(e) = res {
st.err = Some((e.kind(), e.to_string()));
st.chunks.clear();
st.buffered = 0;
st.closed = true;
drop(st);
p.mailbox.complete(p.id);
return;
}
drop(st);
p.mailbox.complete(p.id);
});
if let Err(e) = spawned {
let mut st = lock(&pipe.state);
st.err = Some((e.kind(), e.to_string()));
st.closed = true;
}
pipe
}
fn broken(st: &OutState) -> Option<io::Error> {
if let Some((kind, msg)) = &st.err {
return Some(io::Error::new(*kind, msg.clone()));
}
if st.closing || st.closed {
return Some(io::Error::new(io::ErrorKind::BrokenPipe, "stdin is closed"));
}
None
}
fn poll_write(&self, data: &[u8], cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
register_waker(self.id, cx.waker());
let mut st = lock(&self.state);
if let Some(e) = Self::broken(&st) {
unregister_waker(self.id);
return Poll::Ready(Err(e));
}
if data.is_empty() {
unregister_waker(self.id);
return Poll::Ready(Ok(0));
}
if st.buffered >= MAX_BUFFERED {
return Poll::Pending;
}
st.chunks.push_back(data.to_vec());
st.buffered += data.len();
drop(st);
self.work.notify_one();
unregister_waker(self.id);
Poll::Ready(Ok(data.len()))
}
fn poll_flush(&self, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
register_waker(self.id, cx.waker());
let st = lock(&self.state);
if let Some((kind, msg)) = &st.err {
unregister_waker(self.id);
return Poll::Ready(Err(io::Error::new(*kind, msg.clone())));
}
if st.chunks.is_empty() && !st.writing {
unregister_waker(self.id);
return Poll::Ready(Ok(()));
}
Poll::Pending
}
fn start_close(&self) {
lock(&self.state).closing = true;
self.work.notify_one();
}
fn poll_closed(&self, cx: &mut Context<'_>) -> Poll<()> {
register_waker(self.id, cx.waker());
if lock(&self.state).closed {
unregister_waker(self.id);
return Poll::Ready(());
}
Poll::Pending
}
}
pub struct ChildStdin {
pipe: Arc<PipeOut>,
}
impl ChildStdin {
pub async fn write<B>(&self, buf: B) -> BufResult<usize, B>
where
B: IoBuf + Send + 'static,
{
let data = unsafe { std::slice::from_raw_parts(buf.stable_ptr(), buf.bytes_init()) };
let res = poll_fn(|cx| self.pipe.poll_write(data, cx)).await;
(res, buf)
}
pub async fn write_all<B>(&self, buf: B) -> BufResult<(), B>
where
B: IoBuf + Send + 'static,
{
let (res, buf) = self.write(buf).await;
(res.map(|_| ()), buf)
}
pub async fn flush(&self) -> io::Result<()> {
poll_fn(|cx| self.pipe.poll_flush(cx)).await
}
pub async fn shutdown(&self) -> io::Result<()> {
let flushed = self.flush().await;
self.pipe.start_close();
poll_fn(|cx| self.pipe.poll_closed(cx)).await;
flushed
}
}
impl Drop for ChildStdin {
fn drop(&mut self) {
unregister_waker(self.pipe.id);
self.pipe.start_close();
}
}
pub struct Command {
inner: std::process::Command,
kill_on_drop: bool,
}
impl Command {
pub fn new(program: impl AsRef<OsStr>) -> Self {
Self {
inner: std::process::Command::new(program),
kill_on_drop: false,
}
}
pub fn arg(&mut self, arg: impl AsRef<OsStr>) -> &mut Self {
self.inner.arg(arg);
self
}
pub fn args<I, S>(&mut self, args: I) -> &mut Self
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
self.inner.args(args);
self
}
pub fn env(&mut self, key: impl AsRef<OsStr>, val: impl AsRef<OsStr>) -> &mut Self {
self.inner.env(key, val);
self
}
pub fn envs<I, K, V>(&mut self, vars: I) -> &mut Self
where
I: IntoIterator<Item = (K, V)>,
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
self.inner.envs(vars);
self
}
pub fn env_remove(&mut self, key: impl AsRef<OsStr>) -> &mut Self {
self.inner.env_remove(key);
self
}
pub fn env_clear(&mut self) -> &mut Self {
self.inner.env_clear();
self
}
pub fn current_dir(&mut self, dir: impl AsRef<Path>) -> &mut Self {
self.inner.current_dir(dir);
self
}
pub fn stdin(&mut self, cfg: impl Into<Stdio>) -> &mut Self {
self.inner.stdin(cfg);
self
}
pub fn stdout(&mut self, cfg: impl Into<Stdio>) -> &mut Self {
self.inner.stdout(cfg);
self
}
pub fn stderr(&mut self, cfg: impl Into<Stdio>) -> &mut Self {
self.inner.stderr(cfg);
self
}
pub fn kill_on_drop(&mut self, kill: bool) -> &mut Self {
self.kill_on_drop = kill;
self
}
pub fn spawn(&mut self) -> io::Result<Child> {
let mut child = self.inner.spawn()?;
let stdin = child.stdin.take().map(|s| ChildStdin {
pipe: PipeOut::spawn(s),
});
let stdout = child.stdout.take().map(|s| ChildStdout {
pipe: PipeIn::spawn(s),
});
let stderr = child.stderr.take().map(|s| ChildStderr {
pipe: PipeIn::spawn(s),
});
Ok(Child {
inner: Arc::new(Mutex::new(child)),
stdin,
stdout,
stderr,
kill_on_drop: self.kill_on_drop,
status: None,
})
}
pub async fn output(&mut self) -> io::Result<Output> {
self.inner
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped());
let mut child = self.spawn()?;
let out = child.stdout.take().expect("stdout was piped");
let err = child.stderr.take().expect("stderr was piped");
let (mut stdout, mut stderr) = (Vec::new(), Vec::new());
let (a, b) = crate::join2(out.read_to_end(&mut stdout), err.read_to_end(&mut stderr)).await;
a?;
b?;
let status = child.wait().await?;
Ok(Output {
status,
stdout,
stderr,
})
}
pub async fn status(&mut self) -> io::Result<ExitStatus> {
self.spawn()?.wait().await
}
}
pub struct Child {
inner: Arc<Mutex<StdChild>>,
pub stdin: Option<ChildStdin>,
pub stdout: Option<ChildStdout>,
pub stderr: Option<ChildStderr>,
kill_on_drop: bool,
status: Option<ExitStatus>,
}
impl Child {
pub fn id(&self) -> u32 {
lock(&self.inner).id()
}
pub async fn wait(&mut self) -> io::Result<ExitStatus> {
if let Some(s) = self.status {
return Ok(s);
}
self.stdin.take();
let inner = self.inner.clone();
let status = spawn_blocking(move || {
let mut delay = Duration::from_micros(500);
loop {
match lock(&inner).try_wait() {
Ok(Some(s)) => return Ok(s),
Ok(None) => {}
Err(e) => return Err(e),
}
std::thread::sleep(delay);
delay = (delay * 2).min(Duration::from_millis(20));
}
})
.await
.map_err(|e| io::Error::other(format!("waiting for the child failed: {e}")))??;
self.status = Some(status);
Ok(status)
}
pub fn try_wait(&mut self) -> io::Result<Option<ExitStatus>> {
if let Some(s) = self.status {
return Ok(Some(s));
}
let status = lock(&self.inner).try_wait()?;
self.status = status;
Ok(status)
}
pub fn start_kill(&mut self) -> io::Result<()> {
if self.status.is_some() {
return Ok(());
}
lock(&self.inner).kill()
}
pub async fn kill(&mut self) -> io::Result<()> {
self.start_kill()?;
self.wait().await.map(|_| ())
}
}
impl Drop for Child {
fn drop(&mut self) {
if self.status.is_some() {
return;
}
if self.kill_on_drop {
let _ = lock(&self.inner).kill();
}
let inner = self.inner.clone();
run_detached(move || {
let _ = lock(&inner).wait();
});
}
}