-
Notifications
You must be signed in to change notification settings - Fork 631
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add AsyncWriteExt::write_all_vectored utility
This adds a new feature to future-util, named write_all_vectored, to enable the utility since it requires the unstable io_slice_advance Rust feature. This matches the same API found in io::Write::write_all_vectored in the std lib.
- Loading branch information
1 parent
28dfa30
commit d7d8216
Showing
6 changed files
with
263 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
use futures_core::future::Future; | ||
use futures_core::task::{Context, Poll}; | ||
use futures_io::AsyncWrite; | ||
use futures_io::IoSlice; | ||
use std::io; | ||
use std::mem; | ||
use std::pin::Pin; | ||
|
||
/// Future for the | ||
/// [`write_all_vectored`](super::AsyncWriteExt::write_all_vectored) method. | ||
#[derive(Debug)] | ||
#[must_use = "futures do nothing unless you `.await` or poll them"] | ||
pub struct WriteAllVectored<'a, W: ?Sized + Unpin> { | ||
writer: &'a mut W, | ||
bufs: &'a mut [IoSlice<'a>], | ||
} | ||
|
||
impl<W: ?Sized + Unpin> Unpin for WriteAllVectored<'_, W> {} | ||
|
||
impl<'a, W: AsyncWrite + ?Sized + Unpin> WriteAllVectored<'a, W> { | ||
pub(super) fn new(writer: &'a mut W, bufs: &'a mut [IoSlice<'a>]) -> Self { | ||
WriteAllVectored { writer, bufs } | ||
} | ||
} | ||
|
||
impl<W: AsyncWrite + ?Sized + Unpin> Future for WriteAllVectored<'_, W> { | ||
type Output = io::Result<()>; | ||
|
||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> { | ||
let this = &mut *self; | ||
while !this.bufs.is_empty() { | ||
let n = ready!(Pin::new(&mut this.writer).poll_write_vectored(cx, this.bufs))?; | ||
if n == 0 { | ||
return Poll::Ready(Err(io::ErrorKind::WriteZero.into())); | ||
} else { | ||
this.bufs = IoSlice::advance(mem::take(&mut this.bufs), n); | ||
} | ||
} | ||
|
||
Poll::Ready(Ok(())) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use std::cmp::min; | ||
use std::future::Future; | ||
use std::io; | ||
use std::pin::Pin; | ||
use std::task::{Context, Poll}; | ||
|
||
use crate::io::{AsyncWrite, AsyncWriteExt, IoSlice}; | ||
use crate::task::noop_waker; | ||
|
||
/// Create a new writer that reads from at most `n_bufs` and reads | ||
/// `per_call` bytes (in total) per call to write. | ||
fn test_writer(n_bufs: usize, per_call: usize) -> TestWriter { | ||
TestWriter { | ||
n_bufs, | ||
per_call, | ||
written: Vec::new(), | ||
} | ||
} | ||
|
||
// TODO: maybe move this the future-test crate? | ||
struct TestWriter { | ||
n_bufs: usize, | ||
per_call: usize, | ||
written: Vec<u8>, | ||
} | ||
|
||
impl AsyncWrite for TestWriter { | ||
fn poll_write( | ||
self: Pin<&mut Self>, | ||
cx: &mut Context<'_>, | ||
buf: &[u8], | ||
) -> Poll<io::Result<usize>> { | ||
self.poll_write_vectored(cx, &[IoSlice::new(buf)]) | ||
} | ||
|
||
fn poll_write_vectored( | ||
mut self: Pin<&mut Self>, | ||
_cx: &mut Context<'_>, | ||
bufs: &[IoSlice<'_>], | ||
) -> Poll<io::Result<usize>> { | ||
let mut left = self.per_call; | ||
let mut written = 0; | ||
for buf in bufs.iter().take(self.n_bufs) { | ||
let n = min(left, buf.len()); | ||
self.written.extend_from_slice(&buf[0..n]); | ||
left -= n; | ||
written += n; | ||
} | ||
Poll::Ready(Ok(written)) | ||
} | ||
|
||
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> { | ||
Poll::Ready(Ok(())) | ||
} | ||
|
||
fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> { | ||
Poll::Ready(Ok(())) | ||
} | ||
} | ||
|
||
// TODO: maybe move this the future-test crate? | ||
macro_rules! assert_poll_ok { | ||
($e:expr, $expected:expr) => { | ||
let expected = $expected; | ||
match $e { | ||
Poll::Ready(Ok(ok)) if ok == expected => {} | ||
got => panic!( | ||
"unexpected result, got: {:?}, wanted: Ready(Ok({:?}))", | ||
got, expected | ||
), | ||
} | ||
}; | ||
} | ||
|
||
#[test] | ||
fn test_writer_read_from_one_buf() { | ||
let waker = noop_waker(); | ||
let mut cx = Context::from_waker(&waker); | ||
|
||
let mut dst = test_writer(1, 2); | ||
let mut dst = Pin::new(&mut dst); | ||
|
||
assert_poll_ok!(dst.as_mut().poll_write(&mut cx, &[]), 0); | ||
assert_poll_ok!(dst.as_mut().poll_write_vectored(&mut cx, &[]), 0); | ||
|
||
// Read at most 2 bytes. | ||
assert_poll_ok!(dst.as_mut().poll_write(&mut cx, &[1, 1, 1]), 2); | ||
let bufs = &[IoSlice::new(&[2, 2, 2])]; | ||
assert_poll_ok!(dst.as_mut().poll_write_vectored(&mut cx, bufs), 2); | ||
|
||
// Only read from first buf. | ||
let bufs = &[IoSlice::new(&[3]), IoSlice::new(&[4, 4])]; | ||
assert_poll_ok!(dst.as_mut().poll_write_vectored(&mut cx, bufs), 1); | ||
|
||
assert_eq!(dst.written, &[1, 1, 2, 2, 3]); | ||
} | ||
|
||
#[test] | ||
fn test_writer_read_from_multiple_bufs() { | ||
let waker = noop_waker(); | ||
let mut cx = Context::from_waker(&waker); | ||
|
||
let mut dst = test_writer(3, 3); | ||
let mut dst = Pin::new(&mut dst); | ||
|
||
// Read at most 3 bytes from two buffers. | ||
let bufs = &[IoSlice::new(&[1]), IoSlice::new(&[2, 2, 2])]; | ||
assert_poll_ok!(dst.as_mut().poll_write_vectored(&mut cx, bufs), 3); | ||
|
||
// Read at most 3 bytes from three buffers. | ||
let bufs = &[ | ||
IoSlice::new(&[3]), | ||
IoSlice::new(&[4]), | ||
IoSlice::new(&[5, 5]), | ||
]; | ||
assert_poll_ok!(dst.as_mut().poll_write_vectored(&mut cx, bufs), 3); | ||
|
||
assert_eq!(dst.written, &[1, 2, 2, 3, 4, 5]); | ||
} | ||
|
||
#[test] | ||
fn test_write_all_vectored() { | ||
let waker = noop_waker(); | ||
let mut cx = Context::from_waker(&waker); | ||
|
||
#[rustfmt::skip] // Becomes unreadable otherwise. | ||
let tests: Vec<(_, &'static [u8])> = vec![ | ||
(vec![], &[]), | ||
(vec![IoSlice::new(&[1])], &[1]), | ||
(vec![IoSlice::new(&[1, 2])], &[1, 2]), | ||
(vec![IoSlice::new(&[1, 2, 3])], &[1, 2, 3]), | ||
(vec![IoSlice::new(&[1, 2, 3, 4])], &[1, 2, 3, 4]), | ||
(vec![IoSlice::new(&[1, 2, 3, 4, 5])], &[1, 2, 3, 4, 5]), | ||
(vec![IoSlice::new(&[1]), IoSlice::new(&[2])], &[1, 2]), | ||
(vec![IoSlice::new(&[1, 1]), IoSlice::new(&[2, 2])], &[1, 1, 2, 2]), | ||
(vec![IoSlice::new(&[1, 1, 1]), IoSlice::new(&[2, 2, 2])], &[1, 1, 1, 2, 2, 2]), | ||
(vec![IoSlice::new(&[1, 1, 1, 1]), IoSlice::new(&[2, 2, 2, 2])], &[1, 1, 1, 1, 2, 2, 2, 2]), | ||
(vec![IoSlice::new(&[1]), IoSlice::new(&[2]), IoSlice::new(&[3])], &[1, 2, 3]), | ||
(vec![IoSlice::new(&[1, 1]), IoSlice::new(&[2, 2]), IoSlice::new(&[3, 3])], &[1, 1, 2, 2, 3, 3]), | ||
(vec![IoSlice::new(&[1, 1, 1]), IoSlice::new(&[2, 2, 2]), IoSlice::new(&[3, 3, 3])], &[1, 1, 1, 2, 2, 2, 3, 3, 3]), | ||
]; | ||
|
||
for (mut input, wanted) in tests.into_iter() { | ||
let mut dst = test_writer(2, 2); | ||
{ | ||
let mut future = dst.write_all_vectored(&mut *input); | ||
match Pin::new(&mut future).poll(&mut cx) { | ||
Poll::Ready(Ok(())) => {} | ||
other => panic!("unexpected result polling future: {:?}", other), | ||
} | ||
} | ||
assert_eq!(&*dst.written, &*wanted); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters