mirror of
https://git.asonix.dog/asonix/pict-rs
synced 2024-12-22 11:21:24 +00:00
Increase concurrency when polling some streams
Specifically when writing streams to files or processes' stdin. This adds the ability to poll the source stream during the write operations which can reduce time waiting for more bytes to write.
This commit is contained in:
parent
3879a45b32
commit
04bde6cf20
3 changed files with 115 additions and 11 deletions
17
src/file.rs
17
src/file.rs
|
@ -25,11 +25,13 @@ pub(crate) async fn write_from_stream(
|
||||||
|
|
||||||
#[cfg(not(feature = "io-uring"))]
|
#[cfg(not(feature = "io-uring"))]
|
||||||
mod tokio_file {
|
mod tokio_file {
|
||||||
use crate::{future::WithPollTimer, store::file_store::FileError, Either};
|
use crate::{
|
||||||
|
future::WithPollTimer, store::file_store::FileError, stream::IntoProgressableStreamer,
|
||||||
|
Either,
|
||||||
|
};
|
||||||
use actix_web::web::{Bytes, BytesMut};
|
use actix_web::web::{Bytes, BytesMut};
|
||||||
use futures_core::Stream;
|
use futures_core::Stream;
|
||||||
use std::{io::SeekFrom, path::Path};
|
use std::{io::SeekFrom, path::Path};
|
||||||
use streem::IntoStreamer;
|
|
||||||
use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
|
use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
|
||||||
use tokio_util::{
|
use tokio_util::{
|
||||||
bytes::Buf,
|
bytes::Buf,
|
||||||
|
@ -62,15 +64,18 @@ mod tokio_file {
|
||||||
S: Stream<Item = std::io::Result<Bytes>>,
|
S: Stream<Item = std::io::Result<Bytes>>,
|
||||||
{
|
{
|
||||||
let stream = std::pin::pin!(stream);
|
let stream = std::pin::pin!(stream);
|
||||||
let mut stream = stream.into_streamer();
|
let mut stream = stream.into_progressable_streamer();
|
||||||
|
|
||||||
while let Some(mut bytes) = stream.try_next().with_poll_timer("try-next").await? {
|
while let Some(mut bytes) = stream.try_next().with_poll_timer("try-next").await? {
|
||||||
tracing::trace!("write_from_stream: looping");
|
tracing::trace!("write_from_stream: looping");
|
||||||
|
|
||||||
while bytes.has_remaining() {
|
while bytes.has_remaining() {
|
||||||
self.inner
|
stream
|
||||||
.write_buf(&mut bytes)
|
.make_progress_with(
|
||||||
.with_poll_timer("write-buf")
|
self.inner
|
||||||
|
.write_buf(&mut bytes)
|
||||||
|
.with_poll_timer("write-buf"),
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
crate::sync::cooperate().await;
|
crate::sync::cooperate().await;
|
||||||
|
|
|
@ -7,7 +7,6 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use futures_core::Stream;
|
use futures_core::Stream;
|
||||||
use streem::IntoStreamer;
|
|
||||||
use tokio::{
|
use tokio::{
|
||||||
io::{AsyncReadExt, AsyncWriteExt},
|
io::{AsyncReadExt, AsyncWriteExt},
|
||||||
process::{Child, ChildStdin, Command},
|
process::{Child, ChildStdin, Command},
|
||||||
|
@ -21,6 +20,7 @@ use crate::{
|
||||||
error_code::ErrorCode,
|
error_code::ErrorCode,
|
||||||
future::{LocalBoxFuture, WithTimeout},
|
future::{LocalBoxFuture, WithTimeout},
|
||||||
read::BoxRead,
|
read::BoxRead,
|
||||||
|
stream::IntoProgressableStreamer,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct MetricsGuard {
|
struct MetricsGuard {
|
||||||
|
@ -237,7 +237,7 @@ impl Process {
|
||||||
Ok(Ok(status)) => Err(ProcessError::Status(command, status)),
|
Ok(Ok(status)) => Err(ProcessError::Status(command, status)),
|
||||||
Ok(Err(e)) => Err(ProcessError::Other(command, e)),
|
Ok(Err(e)) => Err(ProcessError::Other(command, e)),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
let _ = child.kill().await;
|
let _ = child.kill().with_timeout(Duration::from_secs(1)).await;
|
||||||
Err(ProcessError::Timeout(command))
|
Err(ProcessError::Timeout(command))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -249,10 +249,17 @@ impl Process {
|
||||||
{
|
{
|
||||||
self.drive(move |mut stdin| async move {
|
self.drive(move |mut stdin| async move {
|
||||||
let stream = std::pin::pin!(input);
|
let stream = std::pin::pin!(input);
|
||||||
let mut stream = stream.into_streamer();
|
let mut stream = stream.into_progressable_streamer();
|
||||||
|
|
||||||
while let Some(mut bytes) = stream.try_next().await? {
|
while let Some(mut bytes) = stream
|
||||||
match stdin.write_all_buf(&mut bytes).await {
|
.try_next()
|
||||||
|
.with_timeout(Duration::from_secs(5))
|
||||||
|
.await??
|
||||||
|
{
|
||||||
|
match stream
|
||||||
|
.make_progress_with(stdin.write_all_buf(&mut bytes))
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(()) => {}
|
Ok(()) => {}
|
||||||
Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe => break,
|
Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe => break,
|
||||||
Err(e) => return Err(e),
|
Err(e) => return Err(e),
|
||||||
|
@ -306,7 +313,9 @@ impl Process {
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
child
|
child
|
||||||
.kill()
|
.kill()
|
||||||
|
.with_timeout(Duration::from_secs(1))
|
||||||
.await
|
.await
|
||||||
|
.map_err(|_| ProcessError::Timeout(command2.clone()))?
|
||||||
.map_err(|e| ProcessError::Other(command2.clone(), e))?;
|
.map_err(|e| ProcessError::Other(command2.clone(), e))?;
|
||||||
Err(ProcessError::Timeout(command2))
|
Err(ProcessError::Timeout(command2))
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,96 @@ use streem::IntoStreamer;
|
||||||
|
|
||||||
use crate::future::WithMetrics;
|
use crate::future::WithMetrics;
|
||||||
|
|
||||||
|
pub struct ProgressableStreamer<S, I> {
|
||||||
|
inner: S,
|
||||||
|
next: Option<Option<I>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> ProgressableStreamer<S, S::Item>
|
||||||
|
where
|
||||||
|
S: Stream + Unpin,
|
||||||
|
{
|
||||||
|
/// Produces the next item from the stream
|
||||||
|
///
|
||||||
|
/// Cancel Safety
|
||||||
|
///
|
||||||
|
/// This future is safe to drop and re-create before completion
|
||||||
|
async fn next(&mut self) -> Option<S::Item> {
|
||||||
|
std::future::poll_fn(|cx| {
|
||||||
|
if let Some(item) = self.next.take() {
|
||||||
|
std::task::Poll::Ready(item)
|
||||||
|
} else {
|
||||||
|
Pin::new(&mut self.inner).poll_next(cx)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Polls the inner stream to make progress on the next item
|
||||||
|
///
|
||||||
|
/// Cancel Safety
|
||||||
|
///
|
||||||
|
/// This future is safe to drop and re-create before completion
|
||||||
|
pub async fn make_progress(&mut self) {
|
||||||
|
if self.next.is_none() {
|
||||||
|
self.next =
|
||||||
|
Some(std::future::poll_fn(|cx| Pin::new(&mut self.inner).poll_next(cx)).await);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Polls the provided future along with the progress future
|
||||||
|
///
|
||||||
|
/// Cancel Safety
|
||||||
|
///
|
||||||
|
/// This future consumes the provided future, and does not ensure it's cancel safety. If the
|
||||||
|
/// provided future is also cancel safe, then this method will be cancel safe as well.
|
||||||
|
///
|
||||||
|
/// If this future is polled to completion, the provided future is guaranteed to be polled to
|
||||||
|
/// completion as well
|
||||||
|
pub async fn make_progress_with<F: std::future::Future>(&mut self, f: F) -> F::Output {
|
||||||
|
let mut f = std::pin::pin!(f);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
_ = self.make_progress() => {
|
||||||
|
return f.await;
|
||||||
|
}
|
||||||
|
output = &mut f => {
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, T, E> ProgressableStreamer<S, Result<T, E>>
|
||||||
|
where
|
||||||
|
S: Stream<Item = Result<T, E>> + Unpin,
|
||||||
|
{
|
||||||
|
/// Produces the next item from the stream
|
||||||
|
///
|
||||||
|
/// Cancel Safety
|
||||||
|
///
|
||||||
|
/// This future is safe to drop and re-create before completion
|
||||||
|
pub async fn try_next(&mut self) -> Result<Option<T>, E> {
|
||||||
|
self.next().await.transpose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait IntoProgressableStreamer: Stream {
|
||||||
|
fn into_progressable_streamer(self) -> ProgressableStreamer<Self, Self::Item>
|
||||||
|
where
|
||||||
|
Self: Sized + Unpin,
|
||||||
|
{
|
||||||
|
ProgressableStreamer {
|
||||||
|
inner: self,
|
||||||
|
next: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> IntoProgressableStreamer for S where S: Stream {}
|
||||||
|
|
||||||
#[cfg(not(feature = "random-errors"))]
|
#[cfg(not(feature = "random-errors"))]
|
||||||
pub(crate) fn error_injector(
|
pub(crate) fn error_injector(
|
||||||
stream: impl Stream<Item = std::io::Result<Bytes>>,
|
stream: impl Stream<Item = std::io::Result<Bytes>>,
|
||||||
|
|
Loading…
Reference in a new issue