Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit ab97e9c

Browse files
Ensure non-empty buffers for large vectored I/O
`readv` and `writev` are constrained by a platform-specific upper bound on the number of buffers which can be passed. Currently, `read_vectored` and `write_vectored` implementations simply truncate to this limit when larger. However, when the only non-empty buffers are at indices above this limit, they will erroneously return `Ok(0)`. Instead, slice the buffers starting at the first non-empty buffer. This trades a conditional move for a branch, so it's barely a penalty in the common case. The new method `limit_slices` on `IoSlice` and `IoSliceMut` may be generally useful to users like `advance_slices` is, but I have left it as `pub(crate)` for now.
1 parent 0c478fd commit ab97e9c

File tree

5 files changed

+112
-41
lines changed

5 files changed

+112
-41
lines changed

‎library/std/src/io/mod.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@
297297
#[cfg(test)]
298298
mod tests;
299299

300+
use core::intrinsics;
300301
#[unstable(feature = "read_buf", issue = "78485")]
301302
pub use core::io::{BorrowedBuf, BorrowedCursor};
302303
use core::slice::memchr;
@@ -1388,6 +1389,25 @@ impl<'a> IoSliceMut<'a> {
13881389
}
13891390
}
13901391

1392+
/// Limits a slice of buffers to at most `n` buffers.
1393+
///
1394+
/// When the slice contains over `n` buffers, ensure that at least one
1395+
/// non-empty buffer is in the truncated slice, if there is one.
1396+
#[allow(dead_code)] // Not used on all platforms
1397+
#[inline]
1398+
pub(crate) fn limit_slices(bufs: &mut &mut [IoSliceMut<'a>], n: usize) {
1399+
if intrinsics::unlikely(bufs.len() > n) {
1400+
for (i, buf) in bufs.iter().enumerate() {
1401+
if !buf.is_empty() {
1402+
let len = cmp::min(bufs.len() - i, n);
1403+
*bufs = &mut take(bufs)[i..i + len];
1404+
return;
1405+
}
1406+
}
1407+
*bufs = &mut take(bufs)[..0];
1408+
}
1409+
}
1410+
13911411
/// Get the underlying bytes as a mutable slice with the original lifetime.
13921412
///
13931413
/// # Examples
@@ -1549,6 +1569,25 @@ impl<'a> IoSlice<'a> {
15491569
}
15501570
}
15511571

1572+
/// Limits a slice of buffers to at most `n` buffers.
1573+
///
1574+
/// When the slice contains over `n` buffers, ensure that at least one
1575+
/// non-empty buffer is in the truncated slice, if there is one.
1576+
#[allow(dead_code)] // Not used on all platforms
1577+
#[inline]
1578+
pub(crate) fn limit_slices(bufs: &mut &[IoSlice<'a>], n: usize) {
1579+
if intrinsics::unlikely(bufs.len() > n) {
1580+
for (i, buf) in bufs.iter().enumerate() {
1581+
if !buf.is_empty() {
1582+
let len = cmp::min(bufs.len() - i, n);
1583+
*bufs = &bufs[i..i + len];
1584+
return;
1585+
}
1586+
}
1587+
*bufs = &bufs[..0];
1588+
}
1589+
}
1590+
15521591
/// Get the underlying bytes as a slice with the original lifetime.
15531592
///
15541593
/// This doesn't borrow from `self`, so is less restrictive than calling

‎library/std/src/sys/fd/hermit.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
#![unstable(reason = "not public", issue = "none", feature = "fd")]
22

3-
use crate::cmp;
43
use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, Read, SeekFrom};
54
use crate::os::hermit::hermit_abi;
65
use crate::os::hermit::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
@@ -38,12 +37,13 @@ impl FileDesc {
3837
Ok(())
3938
}
4039

41-
pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
40+
pub fn read_vectored(&self, mut bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
41+
IoSliceMut::limit_slices(&mut bufs, max_iov());
4242
let ret = cvt(unsafe {
4343
hermit_abi::readv(
4444
self.as_raw_fd(),
4545
bufs.as_mut_ptr() as *mut hermit_abi::iovec as *const hermit_abi::iovec,
46-
cmp::min(bufs.len(),max_iov()),
46+
bufs.len(),
4747
)
4848
})?;
4949
Ok(ret as usize)
@@ -65,12 +65,13 @@ impl FileDesc {
6565
Ok(result as usize)
6666
}
6767

68-
pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
68+
pub fn write_vectored(&self, mut bufs: &[IoSlice<'_>]) -> io::Result<usize> {
69+
IoSlice::limit_slices(&mut bufs, max_iov());
6970
let ret = cvt(unsafe {
7071
hermit_abi::writev(
7172
self.as_raw_fd(),
7273
bufs.as_ptr() as *const hermit_abi::iovec,
73-
cmp::min(bufs.len(),max_iov()),
74+
bufs.len(),
7475
)
7576
})?;
7677
Ok(ret as usize)

‎library/std/src/sys/fd/unix.rs

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -108,12 +108,13 @@ impl FileDesc {
108108
target_os = "vita",
109109
target_os = "nuttx"
110110
)))]
111-
pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
111+
pub fn read_vectored(&self, mut bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
112+
IoSliceMut::limit_slices(&mut bufs, max_iov());
112113
let ret = cvt(unsafe {
113114
libc::readv(
114115
self.as_raw_fd(),
115116
bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
116-
cmp::min(bufs.len(),max_iov()) as libc::c_int,
117+
bufs.len() as libc::c_int,
117118
)
118119
})?;
119120
Ok(ret as usize)
@@ -198,12 +199,17 @@ impl FileDesc {
198199
target_os = "netbsd",
199200
target_os = "openbsd", // OpenBSD 2.7
200201
))]
201-
pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
202+
pub fn read_vectored_at(
203+
&self,
204+
mut bufs: &mut [IoSliceMut<'_>],
205+
offset: u64,
206+
) -> io::Result<usize> {
207+
IoSliceMut::limit_slices(&mut bufs, max_iov());
202208
let ret = cvt(unsafe {
203209
libc::preadv(
204210
self.as_raw_fd(),
205211
bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
206-
cmp::min(bufs.len(),max_iov()) as libc::c_int,
212+
bufs.len() as libc::c_int,
207213
offset as _,
208214
)
209215
})?;
@@ -235,7 +241,11 @@ impl FileDesc {
235241
// passing 64-bits parameters to syscalls, so we fallback to the default
236242
// implementation if `preadv` is not available.
237243
#[cfg(all(target_os = "android", target_pointer_width = "64"))]
238-
pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
244+
pub fn read_vectored_at(
245+
&self,
246+
mut bufs: &mut [IoSliceMut<'_>],
247+
offset: u64,
248+
) -> io::Result<usize> {
239249
syscall!(
240250
fn preadv(
241251
fd: libc::c_int,
@@ -245,11 +255,12 @@ impl FileDesc {
245255
) -> isize;
246256
);
247257

258+
IoSliceMut::limit_slices(&mut bufs, max_iov());
248259
let ret = cvt(unsafe {
249260
preadv(
250261
self.as_raw_fd(),
251262
bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
252-
cmp::min(bufs.len(),max_iov()) as libc::c_int,
263+
bufs.len() as libc::c_int,
253264
offset as _,
254265
)
255266
})?;
@@ -260,7 +271,11 @@ impl FileDesc {
260271
// FIXME(#115199): Rust currently omits weak function definitions
261272
// and its metadata from LLVM IR.
262273
#[no_sanitize(cfi)]
263-
pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
274+
pub fn read_vectored_at(
275+
&self,
276+
mut bufs: &mut [IoSliceMut<'_>],
277+
offset: u64,
278+
) -> io::Result<usize> {
264279
weak!(
265280
fn preadv64(
266281
fd: libc::c_int,
@@ -272,11 +287,12 @@ impl FileDesc {
272287

273288
match preadv64.get() {
274289
Some(preadv) => {
290+
IoSliceMut::limit_slices(&mut bufs, max_iov());
275291
let ret = cvt(unsafe {
276292
preadv(
277293
self.as_raw_fd(),
278294
bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
279-
cmp::min(bufs.len(),max_iov()) as libc::c_int,
295+
bufs.len() as libc::c_int,
280296
offset as _,
281297
)
282298
})?;
@@ -296,7 +312,11 @@ impl FileDesc {
296312
// These versions may be newer than the minimum supported versions of OS's we support so we must
297313
// use "weak" linking.
298314
#[cfg(target_vendor = "apple")]
299-
pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
315+
pub fn read_vectored_at(
316+
&self,
317+
mut bufs: &mut [IoSliceMut<'_>],
318+
offset: u64,
319+
) -> io::Result<usize> {
300320
weak!(
301321
fn preadv(
302322
fd: libc::c_int,
@@ -308,11 +328,12 @@ impl FileDesc {
308328

309329
match preadv.get() {
310330
Some(preadv) => {
331+
IoSliceMut::limit_slices(&mut bufs, max_iov());
311332
let ret = cvt(unsafe {
312333
preadv(
313334
self.as_raw_fd(),
314335
bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
315-
cmp::min(bufs.len(),max_iov()) as libc::c_int,
336+
bufs.len() as libc::c_int,
316337
offset as _,
317338
)
318339
})?;
@@ -339,12 +360,13 @@ impl FileDesc {
339360
target_os = "vita",
340361
target_os = "nuttx"
341362
)))]
342-
pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
363+
pub fn write_vectored(&self, mut bufs: &[IoSlice<'_>]) -> io::Result<usize> {
364+
IoSlice::limit_slices(&mut bufs, max_iov());
343365
let ret = cvt(unsafe {
344366
libc::writev(
345367
self.as_raw_fd(),
346368
bufs.as_ptr() as *const libc::iovec,
347-
cmp::min(bufs.len(),max_iov()) as libc::c_int,
369+
bufs.len() as libc::c_int,
348370
)
349371
})?;
350372
Ok(ret as usize)
@@ -408,12 +430,13 @@ impl FileDesc {
408430
target_os = "netbsd",
409431
target_os = "openbsd", // OpenBSD 2.7
410432
))]
411-
pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
433+
pub fn write_vectored_at(&self, mut bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
434+
IoSlice::limit_slices(&mut bufs, max_iov());
412435
let ret = cvt(unsafe {
413436
libc::pwritev(
414437
self.as_raw_fd(),
415438
bufs.as_ptr() as *const libc::iovec,
416-
cmp::min(bufs.len(),max_iov()) as libc::c_int,
439+
bufs.len() as libc::c_int,
417440
offset as _,
418441
)
419442
})?;
@@ -445,7 +468,7 @@ impl FileDesc {
445468
// passing 64-bits parameters to syscalls, so we fallback to the default
446469
// implementation if `pwritev` is not available.
447470
#[cfg(all(target_os = "android", target_pointer_width = "64"))]
448-
pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
471+
pub fn write_vectored_at(&self, mutbufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
449472
syscall!(
450473
fn pwritev(
451474
fd: libc::c_int,
@@ -455,19 +478,20 @@ impl FileDesc {
455478
) -> isize;
456479
);
457480

481+
IoSlice::limit_slices(&mut bufs, max_iov());
458482
let ret = cvt(unsafe {
459483
pwritev(
460484
self.as_raw_fd(),
461485
bufs.as_ptr() as *const libc::iovec,
462-
cmp::min(bufs.len(),max_iov()) as libc::c_int,
486+
bufs.len() as libc::c_int,
463487
offset as _,
464488
)
465489
})?;
466490
Ok(ret as usize)
467491
}
468492

469493
#[cfg(all(target_os = "android", target_pointer_width = "32"))]
470-
pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
494+
pub fn write_vectored_at(&self, mutbufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
471495
weak!(
472496
fn pwritev64(
473497
fd: libc::c_int,
@@ -479,11 +503,12 @@ impl FileDesc {
479503

480504
match pwritev64.get() {
481505
Some(pwritev) => {
506+
IoSlice::limit_slices(&mut bufs, max_iov());
482507
let ret = cvt(unsafe {
483508
pwritev(
484509
self.as_raw_fd(),
485510
bufs.as_ptr() as *const libc::iovec,
486-
cmp::min(bufs.len(),max_iov()) as libc::c_int,
511+
bufs.len() as libc::c_int,
487512
offset as _,
488513
)
489514
})?;
@@ -503,7 +528,7 @@ impl FileDesc {
503528
// These versions may be newer than the minimum supported versions of OS's we support so we must
504529
// use "weak" linking.
505530
#[cfg(target_vendor = "apple")]
506-
pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
531+
pub fn write_vectored_at(&self, mutbufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
507532
weak!(
508533
fn pwritev(
509534
fd: libc::c_int,
@@ -515,11 +540,12 @@ impl FileDesc {
515540

516541
match pwritev.get() {
517542
Some(pwritev) => {
543+
IoSlice::limit_slices(&mut bufs, max_iov());
518544
let ret = cvt(unsafe {
519545
pwritev(
520546
self.as_raw_fd(),
521547
bufs.as_ptr() as *const libc::iovec,
522-
cmp::min(bufs.len(),max_iov()) as libc::c_int,
548+
bufs.len() as libc::c_int,
523549
offset as _,
524550
)
525551
})?;

‎library/std/src/sys/fd/unix/tests.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,23 @@
11
use core::mem::ManuallyDrop;
22

3-
use super::FileDesc;
3+
use super::{FileDesc, max_iov};
44
use crate::io::IoSlice;
55
use crate::os::unix::io::FromRawFd;
66

77
#[test]
88
fn limit_vector_count() {
9+
const IOV_MAX: usize = max_iov();
10+
911
let stdout = ManuallyDrop::new(unsafe { FileDesc::from_raw_fd(1) });
10-
let bufs = (0..1500).map(|_| IoSlice::new(&[])).collect::<Vec<_>>();
11-
assert!(stdout.write_vectored(&bufs).is_ok());
12+
let mut bufs = vec![IoSlice::new(&[]); IOV_MAX * 2 + 1];
13+
assert_eq!(stdout.write_vectored(&bufs).unwrap(), 0);
14+
15+
// The slice of buffers is truncated to IOV_MAX buffers. However, since the
16+
// first IOV_MAX buffers are all empty, it is sliced starting at the first
17+
// non-empty buffer to avoid erroneously returning Ok(0). In this case, that
18+
// starts with the b"hello" buffer and ends just before the b"world!"
19+
// buffer.
20+
bufs[IOV_MAX] = IoSlice::new(b"hello");
21+
bufs[IOV_MAX * 2] = IoSlice::new(b"world!");
22+
assert_eq!(stdout.write_vectored(&bufs).unwrap(), b"hello".len())
1223
}

‎library/std/src/sys/net/connection/socket/solid.rs

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::os::solid::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, Owne
99
use crate::sys::abi;
1010
use crate::sys_common::{FromInner, IntoInner};
1111
use crate::time::Duration;
12-
use crate::{cmp,mem, ptr, str};
12+
use crate::{mem, ptr, str};
1313

1414
pub(super) mod netc {
1515
pub use crate::sys::abi::sockets::*;
@@ -222,13 +222,10 @@ impl Socket {
222222
self.recv_with_flags(buf, 0)
223223
}
224224

225-
pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
225+
pub fn read_vectored(&self, mut bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
226+
IoSliceMut::limit_slices(&mut bufs, max_iov());
226227
let ret = cvt(unsafe {
227-
netc::readv(
228-
self.as_raw_fd(),
229-
bufs.as_ptr() as *const netc::iovec,
230-
cmp::min(bufs.len(), max_iov()) as c_int,
231-
)
228+
netc::readv(self.as_raw_fd(), bufs.as_ptr() as *const netc::iovec, bufs.len() as c_int)
232229
})?;
233230
Ok(ret as usize)
234231
}
@@ -267,13 +264,10 @@ impl Socket {
267264
self.recv_from_with_flags(buf, MSG_PEEK)
268265
}
269266

270-
pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
267+
pub fn write_vectored(&self, mut bufs: &[IoSlice<'_>]) -> io::Result<usize> {
268+
IoSlice::limit_slices(&mut bufs, max_iov());
271269
let ret = cvt(unsafe {
272-
netc::writev(
273-
self.as_raw_fd(),
274-
bufs.as_ptr() as *const netc::iovec,
275-
cmp::min(bufs.len(), max_iov()) as c_int,
276-
)
270+
netc::writev(self.as_raw_fd(), bufs.as_ptr() as *const netc::iovec, bufs.len() as c_int)
277271
})?;
278272
Ok(ret as usize)
279273
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /