Skip to content

Commit d7d8216

Browse files
Thomasdezeeuwcramertj
authored andcommitted
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.
1 parent 28dfa30 commit d7d8216

File tree

6 files changed

+263
-2
lines changed

6 files changed

+263
-2
lines changed

Diff for: futures-util/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ unstable = ["futures-core/unstable", "futures-task/unstable"]
3030
cfg-target-has-atomic = ["futures-core/cfg-target-has-atomic", "futures-task/cfg-target-has-atomic"]
3131
bilock = []
3232
read-initializer = ["io", "futures-io/read-initializer", "futures-io/unstable"]
33+
write-all-vectored = ["io"]
3334

3435
[dependencies]
3536
futures-core = { path = "../futures-core", version = "0.3.4", default-features = false }

Diff for: futures-util/src/io/mod.rs

+59-1
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,11 @@ pub use self::write_vectored::WriteVectored;
124124
mod write_all;
125125
pub use self::write_all::WriteAll;
126126

127+
#[cfg(feature = "write_all_vectored")]
128+
mod write_all_vectored;
129+
#[cfg(feature = "write_all_vectored")]
130+
pub use self::write_all_vectored::WriteAllVectored;
131+
127132
/// An extension trait which adds utility methods to `AsyncRead` types.
128133
pub trait AsyncReadExt: AsyncRead {
129134
/// Creates an adaptor which will chain this stream with another.
@@ -460,6 +465,60 @@ pub trait AsyncWriteExt: AsyncWrite {
460465
WriteAll::new(self, buf)
461466
}
462467

468+
/// Attempts to write multiple buffers into this writer.
469+
///
470+
/// Creates a future that will write the entire contents of `bufs` into this
471+
/// `AsyncWrite` using [vectored writes].
472+
///
473+
/// The returned future will not complete until all the data has been
474+
/// written.
475+
///
476+
/// [vectored writes]: std::io::Write::write_vectored
477+
///
478+
/// # Notes
479+
///
480+
/// Unlike `io::Write::write_vectored`, this takes a *mutable* reference to
481+
/// a slice of `IoSlice`s, not an immutable one. That's because we need to
482+
/// modify the slice to keep track of the bytes already written.
483+
///
484+
/// Once this futures returns, the contents of `bufs` are unspecified, as
485+
/// this depends on how many calls to `write_vectored` were necessary. It is
486+
/// best to understand this function as taking ownership of `bufs` and to
487+
/// not use `bufs` afterwards. The underlying buffers, to which the
488+
/// `IoSlice`s point (but not the `IoSlice`s themselves), are unchanged and
489+
/// can be reused.
490+
///
491+
/// # Examples
492+
///
493+
/// ```
494+
/// # futures::executor::block_on(async {
495+
/// use futures::io::AsyncWriteExt;
496+
/// use std::io::{Cursor, IoSlice};
497+
///
498+
/// let mut writer = Cursor::new([0u8; 7]);
499+
/// let bufs = &mut [
500+
/// IoSlice::new(&[1]),
501+
/// IoSlice::new(&[2, 3]),
502+
/// IoSlice::new(&[4, 5, 6]),
503+
/// ];
504+
///
505+
/// writer.write_all_vectored(bufs).await?;
506+
/// // Note: the contents of `bufs` is now undefined, see the Notes section.
507+
///
508+
/// assert_eq!(writer.into_inner(), [1, 2, 3, 4, 5, 6, 0]);
509+
/// # Ok::<(), Box<dyn std::error::Error>>(()) }).unwrap();
510+
/// ```
511+
#[cfg(feature = "write_all_vectored")]
512+
fn write_all_vectored<'a>(
513+
&'a mut self,
514+
bufs: &'a mut [IoSlice<'a>],
515+
) -> WriteAllVectored<'a, Self>
516+
where
517+
Self: Unpin,
518+
{
519+
WriteAllVectored::new(self, bufs)
520+
}
521+
463522
/// Wraps an [`AsyncWrite`] in a compatibility wrapper that allows it to be
464523
/// used as a futures 0.1 / tokio-io 0.1 `AsyncWrite`.
465524
/// Requires the `io-compat` feature to enable.
@@ -470,7 +529,6 @@ pub trait AsyncWriteExt: AsyncWrite {
470529
Compat::new(self)
471530
}
472531

473-
474532
/// Allow using an [`AsyncWrite`] as a [`Sink`](futures_sink::Sink)`<Item: AsRef<[u8]>>`.
475533
///
476534
/// This adapter produces a sink that will write each value passed to it

Diff for: futures-util/src/io/write_all.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ impl<W: AsyncWrite + ?Sized + Unpin> Future for WriteAll<'_, W> {
3333
this.buf = rest;
3434
}
3535
if n == 0 {
36-
return Poll::Ready(Err(io::ErrorKind::WriteZero.into()))
36+
return Poll::Ready(Err(io::ErrorKind::WriteZero.into()));
3737
}
3838
}
3939

Diff for: futures-util/src/io/write_all_vectored.rs

+200
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
use futures_core::future::Future;
2+
use futures_core::task::{Context, Poll};
3+
use futures_io::AsyncWrite;
4+
use futures_io::IoSlice;
5+
use std::io;
6+
use std::mem;
7+
use std::pin::Pin;
8+
9+
/// Future for the
10+
/// [`write_all_vectored`](super::AsyncWriteExt::write_all_vectored) method.
11+
#[derive(Debug)]
12+
#[must_use = "futures do nothing unless you `.await` or poll them"]
13+
pub struct WriteAllVectored<'a, W: ?Sized + Unpin> {
14+
writer: &'a mut W,
15+
bufs: &'a mut [IoSlice<'a>],
16+
}
17+
18+
impl<W: ?Sized + Unpin> Unpin for WriteAllVectored<'_, W> {}
19+
20+
impl<'a, W: AsyncWrite + ?Sized + Unpin> WriteAllVectored<'a, W> {
21+
pub(super) fn new(writer: &'a mut W, bufs: &'a mut [IoSlice<'a>]) -> Self {
22+
WriteAllVectored { writer, bufs }
23+
}
24+
}
25+
26+
impl<W: AsyncWrite + ?Sized + Unpin> Future for WriteAllVectored<'_, W> {
27+
type Output = io::Result<()>;
28+
29+
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
30+
let this = &mut *self;
31+
while !this.bufs.is_empty() {
32+
let n = ready!(Pin::new(&mut this.writer).poll_write_vectored(cx, this.bufs))?;
33+
if n == 0 {
34+
return Poll::Ready(Err(io::ErrorKind::WriteZero.into()));
35+
} else {
36+
this.bufs = IoSlice::advance(mem::take(&mut this.bufs), n);
37+
}
38+
}
39+
40+
Poll::Ready(Ok(()))
41+
}
42+
}
43+
44+
#[cfg(test)]
45+
mod tests {
46+
use std::cmp::min;
47+
use std::future::Future;
48+
use std::io;
49+
use std::pin::Pin;
50+
use std::task::{Context, Poll};
51+
52+
use crate::io::{AsyncWrite, AsyncWriteExt, IoSlice};
53+
use crate::task::noop_waker;
54+
55+
/// Create a new writer that reads from at most `n_bufs` and reads
56+
/// `per_call` bytes (in total) per call to write.
57+
fn test_writer(n_bufs: usize, per_call: usize) -> TestWriter {
58+
TestWriter {
59+
n_bufs,
60+
per_call,
61+
written: Vec::new(),
62+
}
63+
}
64+
65+
// TODO: maybe move this the future-test crate?
66+
struct TestWriter {
67+
n_bufs: usize,
68+
per_call: usize,
69+
written: Vec<u8>,
70+
}
71+
72+
impl AsyncWrite for TestWriter {
73+
fn poll_write(
74+
self: Pin<&mut Self>,
75+
cx: &mut Context<'_>,
76+
buf: &[u8],
77+
) -> Poll<io::Result<usize>> {
78+
self.poll_write_vectored(cx, &[IoSlice::new(buf)])
79+
}
80+
81+
fn poll_write_vectored(
82+
mut self: Pin<&mut Self>,
83+
_cx: &mut Context<'_>,
84+
bufs: &[IoSlice<'_>],
85+
) -> Poll<io::Result<usize>> {
86+
let mut left = self.per_call;
87+
let mut written = 0;
88+
for buf in bufs.iter().take(self.n_bufs) {
89+
let n = min(left, buf.len());
90+
self.written.extend_from_slice(&buf[0..n]);
91+
left -= n;
92+
written += n;
93+
}
94+
Poll::Ready(Ok(written))
95+
}
96+
97+
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
98+
Poll::Ready(Ok(()))
99+
}
100+
101+
fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
102+
Poll::Ready(Ok(()))
103+
}
104+
}
105+
106+
// TODO: maybe move this the future-test crate?
107+
macro_rules! assert_poll_ok {
108+
($e:expr, $expected:expr) => {
109+
let expected = $expected;
110+
match $e {
111+
Poll::Ready(Ok(ok)) if ok == expected => {}
112+
got => panic!(
113+
"unexpected result, got: {:?}, wanted: Ready(Ok({:?}))",
114+
got, expected
115+
),
116+
}
117+
};
118+
}
119+
120+
#[test]
121+
fn test_writer_read_from_one_buf() {
122+
let waker = noop_waker();
123+
let mut cx = Context::from_waker(&waker);
124+
125+
let mut dst = test_writer(1, 2);
126+
let mut dst = Pin::new(&mut dst);
127+
128+
assert_poll_ok!(dst.as_mut().poll_write(&mut cx, &[]), 0);
129+
assert_poll_ok!(dst.as_mut().poll_write_vectored(&mut cx, &[]), 0);
130+
131+
// Read at most 2 bytes.
132+
assert_poll_ok!(dst.as_mut().poll_write(&mut cx, &[1, 1, 1]), 2);
133+
let bufs = &[IoSlice::new(&[2, 2, 2])];
134+
assert_poll_ok!(dst.as_mut().poll_write_vectored(&mut cx, bufs), 2);
135+
136+
// Only read from first buf.
137+
let bufs = &[IoSlice::new(&[3]), IoSlice::new(&[4, 4])];
138+
assert_poll_ok!(dst.as_mut().poll_write_vectored(&mut cx, bufs), 1);
139+
140+
assert_eq!(dst.written, &[1, 1, 2, 2, 3]);
141+
}
142+
143+
#[test]
144+
fn test_writer_read_from_multiple_bufs() {
145+
let waker = noop_waker();
146+
let mut cx = Context::from_waker(&waker);
147+
148+
let mut dst = test_writer(3, 3);
149+
let mut dst = Pin::new(&mut dst);
150+
151+
// Read at most 3 bytes from two buffers.
152+
let bufs = &[IoSlice::new(&[1]), IoSlice::new(&[2, 2, 2])];
153+
assert_poll_ok!(dst.as_mut().poll_write_vectored(&mut cx, bufs), 3);
154+
155+
// Read at most 3 bytes from three buffers.
156+
let bufs = &[
157+
IoSlice::new(&[3]),
158+
IoSlice::new(&[4]),
159+
IoSlice::new(&[5, 5]),
160+
];
161+
assert_poll_ok!(dst.as_mut().poll_write_vectored(&mut cx, bufs), 3);
162+
163+
assert_eq!(dst.written, &[1, 2, 2, 3, 4, 5]);
164+
}
165+
166+
#[test]
167+
fn test_write_all_vectored() {
168+
let waker = noop_waker();
169+
let mut cx = Context::from_waker(&waker);
170+
171+
#[rustfmt::skip] // Becomes unreadable otherwise.
172+
let tests: Vec<(_, &'static [u8])> = vec![
173+
(vec![], &[]),
174+
(vec![IoSlice::new(&[1])], &[1]),
175+
(vec![IoSlice::new(&[1, 2])], &[1, 2]),
176+
(vec![IoSlice::new(&[1, 2, 3])], &[1, 2, 3]),
177+
(vec![IoSlice::new(&[1, 2, 3, 4])], &[1, 2, 3, 4]),
178+
(vec![IoSlice::new(&[1, 2, 3, 4, 5])], &[1, 2, 3, 4, 5]),
179+
(vec![IoSlice::new(&[1]), IoSlice::new(&[2])], &[1, 2]),
180+
(vec![IoSlice::new(&[1, 1]), IoSlice::new(&[2, 2])], &[1, 1, 2, 2]),
181+
(vec![IoSlice::new(&[1, 1, 1]), IoSlice::new(&[2, 2, 2])], &[1, 1, 1, 2, 2, 2]),
182+
(vec![IoSlice::new(&[1, 1, 1, 1]), IoSlice::new(&[2, 2, 2, 2])], &[1, 1, 1, 1, 2, 2, 2, 2]),
183+
(vec![IoSlice::new(&[1]), IoSlice::new(&[2]), IoSlice::new(&[3])], &[1, 2, 3]),
184+
(vec![IoSlice::new(&[1, 1]), IoSlice::new(&[2, 2]), IoSlice::new(&[3, 3])], &[1, 1, 2, 2, 3, 3]),
185+
(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]),
186+
];
187+
188+
for (mut input, wanted) in tests.into_iter() {
189+
let mut dst = test_writer(2, 2);
190+
{
191+
let mut future = dst.write_all_vectored(&mut *input);
192+
match Pin::new(&mut future).poll(&mut cx) {
193+
Poll::Ready(Ok(())) => {}
194+
other => panic!("unexpected result polling future: {:?}", other),
195+
}
196+
}
197+
assert_eq!(&*dst.written, &*wanted);
198+
}
199+
}
200+
}

Diff for: futures-util/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
44
#![cfg_attr(feature = "cfg-target-has-atomic", feature(cfg_target_has_atomic))]
55
#![cfg_attr(feature = "read-initializer", feature(read_initializer))]
6+
#![cfg_attr(feature = "write-all-vectored", feature(io_slice_advance))]
67

78
#![cfg_attr(not(feature = "std"), no_std)]
89
#![warn(missing_docs, missing_debug_implementations, rust_2018_idioms, unreachable_pub)]

Diff for: futures/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ unstable = ["futures-core/unstable", "futures-task/unstable", "futures-channel/u
5151
cfg-target-has-atomic = ["futures-core/cfg-target-has-atomic", "futures-task/cfg-target-has-atomic", "futures-channel/cfg-target-has-atomic", "futures-util/cfg-target-has-atomic"]
5252
bilock = ["futures-util/bilock"]
5353
read-initializer = ["futures-io/read-initializer", "futures-util/read-initializer"]
54+
write-all-vectored = ["futures-util/write-all-vectored"]
5455

5556
[package.metadata.docs.rs]
5657
all-features = true

0 commit comments

Comments
 (0)