From 94687525815cb2138779e17a766e24c826819d7c Mon Sep 17 00:00:00 2001 From: Adam Reichold Date: Sat, 1 Aug 2020 14:18:11 +0200 Subject: [PATCH 1/5] Query maximum vector count on Linux and macOS Both Linux and MacOS enforce limits on the vector count when performing vectored I/O via the readv and writev system calls and return EINVAL when these limits are exceeded. This changes the standard library to handle those limits as short reads and writes to avoid forcing its users to query these limits using platform specific mechanisms. --- library/std/src/sys/unix/fd.rs | 38 ++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/library/std/src/sys/unix/fd.rs b/library/std/src/sys/unix/fd.rs index 84c4d662161..675528bd526 100644 --- a/library/std/src/sys/unix/fd.rs +++ b/library/std/src/sys/unix/fd.rs @@ -26,6 +26,27 @@ const READ_LIMIT: usize = c_int::MAX as usize - 1; #[cfg(not(target_os = "macos"))] const READ_LIMIT: usize = libc::ssize_t::MAX as usize; +#[cfg(any(target_os = "linux", target_os = "macos"))] +fn max_iov() -> c_int { + let ret = unsafe { + libc::sysconf( + #[cfg(target_os = "linux")] + libc::_SC_IOV_MAX, + #[cfg(target_os = "macos")] + libc::_SC_UIO_MAXIOV, + ) + }; + + // 1024 is the default value on modern Linux systems + // and hopefully more useful than `c_int::MAX`. + if ret > 0 { ret as c_int } else { 1024 } +} + +#[cfg(not(any(target_os = "linux", target_os = "macos")))] +fn max_iov() -> c_int { + c_int::MAX +} + impl FileDesc { pub fn new(fd: c_int) -> FileDesc { FileDesc { fd } @@ -54,7 +75,7 @@ impl FileDesc { libc::readv( self.fd, bufs.as_ptr() as *const libc::iovec, - cmp::min(bufs.len(), c_int::MAX as usize) as c_int, + cmp::min(bufs.len(), max_iov() as usize) as c_int, ) })?; Ok(ret as usize) @@ -111,7 +132,7 @@ impl FileDesc { libc::writev( self.fd, bufs.as_ptr() as *const libc::iovec, - cmp::min(bufs.len(), c_int::MAX as usize) as c_int, + cmp::min(bufs.len(), max_iov() as usize) as c_int, ) })?; Ok(ret as usize) @@ -256,3 +277,16 @@ impl Drop for FileDesc { let _ = unsafe { libc::close(self.fd) }; } } + +#[cfg(test)] +mod tests { + use super::{FileDesc, IoSlice}; + + #[test] + fn limit_vector_count() { + let stdout = FileDesc { fd: 1 }; + let bufs = (0..1500).map(|_| IoSlice::new(&[])).collect::>(); + + assert!(stdout.write_vectored(&bufs).is_ok()); + } +} From 6672f7be032e1fc392cb0e2a785563b82b095117 Mon Sep 17 00:00:00 2001 From: Adam Reichold Date: Sat, 1 Aug 2020 14:29:42 +0200 Subject: [PATCH 2/5] Memoize the I/O vector count limit Keep the I/O vector count limit in a `SyncOnceCell` to avoid the overhead of repeatedly calling `sysconf` as these limits are guaranteed to not change during the lifetime of a process by POSIX. --- library/std/src/sys/unix/fd.rs | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/library/std/src/sys/unix/fd.rs b/library/std/src/sys/unix/fd.rs index 675528bd526..e294df98c60 100644 --- a/library/std/src/sys/unix/fd.rs +++ b/library/std/src/sys/unix/fd.rs @@ -2,6 +2,7 @@ use crate::cmp; use crate::io::{self, Initializer, IoSlice, IoSliceMut, Read}; +use crate::lazy::SyncOnceCell; use crate::mem; use crate::sys::cvt; use crate::sys_common::AsInner; @@ -28,18 +29,22 @@ const READ_LIMIT: usize = libc::ssize_t::MAX as usize; #[cfg(any(target_os = "linux", target_os = "macos"))] fn max_iov() -> c_int { - let ret = unsafe { - libc::sysconf( - #[cfg(target_os = "linux")] - libc::_SC_IOV_MAX, - #[cfg(target_os = "macos")] - libc::_SC_UIO_MAXIOV, - ) - }; + static LIM: SyncOnceCell = SyncOnceCell::new(); - // 1024 is the default value on modern Linux systems - // and hopefully more useful than `c_int::MAX`. - if ret > 0 { ret as c_int } else { 1024 } + *LIM.get_or_init(|| { + let ret = unsafe { + libc::sysconf( + #[cfg(target_os = "linux")] + libc::_SC_IOV_MAX, + #[cfg(target_os = "macos")] + libc::_SC_UIO_MAXIOV, + ) + }; + + // 1024 is the default value on modern Linux systems + // and hopefully more useful than `c_int::MAX`. + if ret > 0 { ret as c_int } else { 1024 } + }) } #[cfg(not(any(target_os = "linux", target_os = "macos")))] From 87edccf0f048d566596495650188eea15cdf62d6 Mon Sep 17 00:00:00 2001 From: Adam Reichold Date: Sat, 1 Aug 2020 15:38:08 +0200 Subject: [PATCH 3/5] Reduce synchronization overhead of I/O vector count memoization --- library/std/src/sys/unix/fd.rs | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/library/std/src/sys/unix/fd.rs b/library/std/src/sys/unix/fd.rs index e294df98c60..91116a53235 100644 --- a/library/std/src/sys/unix/fd.rs +++ b/library/std/src/sys/unix/fd.rs @@ -2,8 +2,8 @@ use crate::cmp; use crate::io::{self, Initializer, IoSlice, IoSliceMut, Read}; -use crate::lazy::SyncOnceCell; use crate::mem; +use crate::sync::atomic::{AtomicUsize, Ordering}; use crate::sys::cvt; use crate::sys_common::AsInner; @@ -28,10 +28,11 @@ const READ_LIMIT: usize = c_int::MAX as usize - 1; const READ_LIMIT: usize = libc::ssize_t::MAX as usize; #[cfg(any(target_os = "linux", target_os = "macos"))] -fn max_iov() -> c_int { - static LIM: SyncOnceCell = SyncOnceCell::new(); +fn max_iov() -> usize { + static LIM: AtomicUsize = AtomicUsize::new(0); - *LIM.get_or_init(|| { + let mut lim = LIM.load(Ordering::Relaxed); + if lim == 0 { let ret = unsafe { libc::sysconf( #[cfg(target_os = "linux")] @@ -43,13 +44,16 @@ fn max_iov() -> c_int { // 1024 is the default value on modern Linux systems // and hopefully more useful than `c_int::MAX`. - if ret > 0 { ret as c_int } else { 1024 } - }) + lim = if ret > 0 { ret as usize } else { 1024 }; + LIM.store(lim, Ordering::Relaxed); + } + + lim } #[cfg(not(any(target_os = "linux", target_os = "macos")))] -fn max_iov() -> c_int { - c_int::MAX +fn max_iov() -> usize { + c_int::MAX as usize } impl FileDesc { @@ -80,7 +84,7 @@ impl FileDesc { libc::readv( self.fd, bufs.as_ptr() as *const libc::iovec, - cmp::min(bufs.len(), max_iov() as usize) as c_int, + cmp::min(bufs.len(), max_iov()) as c_int, ) })?; Ok(ret as usize) @@ -137,7 +141,7 @@ impl FileDesc { libc::writev( self.fd, bufs.as_ptr() as *const libc::iovec, - cmp::min(bufs.len(), max_iov() as usize) as c_int, + cmp::min(bufs.len(), max_iov()) as c_int, ) })?; Ok(ret as usize) From 04a0114e7ecc44049425aaf8f7ba7f66da80e0c4 Mon Sep 17 00:00:00 2001 From: Adam Reichold Date: Sat, 1 Aug 2020 16:06:00 +0200 Subject: [PATCH 4/5] Rely only on POSIX semantics for I/O vector count All #[cfg(unix)] platforms follow the POSIX standard and define _SC_IOV_MAX so that we rely purely on POSIX semantics to determine the limits on I/O vector count. --- library/std/src/sys/unix/fd.rs | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/library/std/src/sys/unix/fd.rs b/library/std/src/sys/unix/fd.rs index 91116a53235..6b8f3ea172d 100644 --- a/library/std/src/sys/unix/fd.rs +++ b/library/std/src/sys/unix/fd.rs @@ -27,35 +27,21 @@ const READ_LIMIT: usize = c_int::MAX as usize - 1; #[cfg(not(target_os = "macos"))] const READ_LIMIT: usize = libc::ssize_t::MAX as usize; -#[cfg(any(target_os = "linux", target_os = "macos"))] fn max_iov() -> usize { static LIM: AtomicUsize = AtomicUsize::new(0); let mut lim = LIM.load(Ordering::Relaxed); if lim == 0 { - let ret = unsafe { - libc::sysconf( - #[cfg(target_os = "linux")] - libc::_SC_IOV_MAX, - #[cfg(target_os = "macos")] - libc::_SC_UIO_MAXIOV, - ) - }; + let ret = unsafe { libc::sysconf(libc::_SC_IOV_MAX) }; - // 1024 is the default value on modern Linux systems - // and hopefully more useful than `c_int::MAX`. - lim = if ret > 0 { ret as usize } else { 1024 }; + // 16 is the minimum value required by POSIX. + lim = if ret > 0 { ret as usize } else { 16 }; LIM.store(lim, Ordering::Relaxed); } lim } -#[cfg(not(any(target_os = "linux", target_os = "macos")))] -fn max_iov() -> usize { - c_int::MAX as usize -} - impl FileDesc { pub fn new(fd: c_int) -> FileDesc { FileDesc { fd } From 9073acdc9888acd2ba66601a77b96fbda67ac8c7 Mon Sep 17 00:00:00 2001 From: Adam Reichold Date: Wed, 5 Aug 2020 16:56:51 +0200 Subject: [PATCH 5/5] Add fallback for cfg(unix) targets that do not define libc::_SC_IOV_MAX. --- library/std/src/sys/unix/fd.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/library/std/src/sys/unix/fd.rs b/library/std/src/sys/unix/fd.rs index 6b8f3ea172d..e36a53084ba 100644 --- a/library/std/src/sys/unix/fd.rs +++ b/library/std/src/sys/unix/fd.rs @@ -3,6 +3,7 @@ use crate::cmp; use crate::io::{self, Initializer, IoSlice, IoSliceMut, Read}; use crate::mem; +#[cfg(not(any(target_os = "redox", target_env = "newlib")))] use crate::sync::atomic::{AtomicUsize, Ordering}; use crate::sys::cvt; use crate::sys_common::AsInner; @@ -27,6 +28,7 @@ const READ_LIMIT: usize = c_int::MAX as usize - 1; #[cfg(not(target_os = "macos"))] const READ_LIMIT: usize = libc::ssize_t::MAX as usize; +#[cfg(not(any(target_os = "redox", target_env = "newlib")))] fn max_iov() -> usize { static LIM: AtomicUsize = AtomicUsize::new(0); @@ -42,6 +44,11 @@ fn max_iov() -> usize { lim } +#[cfg(any(target_os = "redox", target_env = "newlib"))] +fn max_iov() -> usize { + 16 // The minimum value required by POSIX. +} + impl FileDesc { pub fn new(fd: c_int) -> FileDesc { FileDesc { fd }