use tokio::io::AsyncWriteExt;
use tokio::process::ChildStdin;
use tokio::sync::{mpsc, oneshot};
use tokio::task::JoinHandle;
pub(crate) enum StdinRequest {
Write(Vec<u8>, oneshot::Sender<std::io::Result<()>>),
Close(oneshot::Sender<std::io::Result<()>>),
}
pub(crate) type StdinSender = mpsc::Sender<StdinRequest>;
pub(crate) type StdinReceiver = mpsc::Receiver<StdinRequest>;
pub(crate) fn channel() -> (StdinSender, StdinReceiver) {
mpsc::channel(64)
}
pub(crate) fn serve(
mut stdin: ChildStdin,
initial: Option<Vec<u8>>,
mut requests: StdinReceiver,
) -> JoinHandle<()> {
tokio::spawn(async move {
if let Some(content) = initial {
if stdin.write_all(&content).await.is_err() {
return;
}
}
while let Some(request) = requests.recv().await {
match request {
StdinRequest::Write(data, ack) => {
let written = match stdin.write_all(&data).await {
Ok(()) => stdin.flush().await,
Err(error) => Err(error),
};
let _ = ack.send(written);
}
StdinRequest::Close(ack) => {
let _ = ack.send(stdin.shutdown().await);
return;
}
}
}
let _ = stdin.shutdown().await;
})
}
fn closed() -> std::io::Error {
std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
"stdin is not open: call StreamingRunner::stdin_pipe(), and write before close_stdin() and before the process exits",
)
}
pub(crate) async fn request(
sender: Option<&StdinSender>,
make: impl FnOnce(oneshot::Sender<std::io::Result<()>>) -> StdinRequest,
) -> std::io::Result<()> {
let sender = sender.ok_or_else(closed)?;
let (ack, done) = oneshot::channel();
sender.send(make(ack)).await.map_err(|_| closed())?;
done.await.map_err(|_| closed())?
}