Skip to content

Commit 227421a

Browse files
committed
File: Try doing a non-blocking read before punting to the threadpool
...on Linux. If the data is already available in cache this will avoid cross-thread interaction and remove a copy. It should help with latency too as reads that can be satisfied now won't need to wait in queue until other fs operations are complete.
1 parent e827829 commit 227421a

File tree

2 files changed

+78
-3
lines changed

2 files changed

+78
-3
lines changed

tokio/Cargo.toml

+3-3
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ full = [
4343
"time",
4444
]
4545

46-
fs = []
46+
fs = ["libc"]
4747
io-util = ["memchr", "bytes"]
4848
# stdin, stdout, stderr
4949
io-std = []
@@ -104,11 +104,11 @@ parking_lot = { version = "0.11.0", optional = true }
104104
tracing = { version = "0.1.21", default-features = false, features = ["std"], optional = true } # Not in full
105105

106106
[target.'cfg(unix)'.dependencies]
107-
libc = { version = "0.2.42", optional = true }
107+
libc = { git = "https://github.com/rust-lang/libc", rev = "a61fd8c79c21b387cdf6e4bcabc8b04afe594960", optional = true }
108108
signal-hook-registry = { version = "1.1.1", optional = true }
109109

110110
[target.'cfg(unix)'.dev-dependencies]
111-
libc = { version = "0.2.42" }
111+
libc = { git = "https://github.com/rust-lang/libc", rev = "a61fd8c79c21b387cdf6e4bcabc8b04afe594960" }
112112
nix = { version = "0.19.0" }
113113

114114
[target.'cfg(windows)'.dependencies.winapi]

tokio/src/fs/file.rs

+75
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,10 @@ impl AsyncRead for File {
499499
return Ready(Ok(()));
500500
}
501501

502+
if let Some(x) = read_nowait::try_nonblocking_read(me.std.as_ref(), dst) {
503+
return Ready(x);
504+
}
505+
502506
buf.ensure_capacity_for(dst);
503507
let std = me.std.clone();
504508

@@ -756,3 +760,74 @@ impl Inner {
756760
}
757761
}
758762
}
763+
764+
#[cfg(all(target_os = "linux", not(test)))]
765+
mod read_nowait {
766+
use crate::io::ReadBuf;
767+
use libc::{c_int, c_void, iovec, off_t, preadv2};
768+
use std::{
769+
os::unix::prelude::AsRawFd,
770+
sync::atomic::{AtomicBool, Ordering},
771+
};
772+
773+
static NONBLOCKING_READ_SUPPORTED: AtomicBool = AtomicBool::new(true);
774+
775+
pub(crate) fn try_nonblocking_read(
776+
file: &crate::fs::sys::File,
777+
dst: &mut ReadBuf<'_>,
778+
) -> Option<std::io::Result<()>> {
779+
if !NONBLOCKING_READ_SUPPORTED.load(Ordering::Relaxed) {
780+
return None;
781+
}
782+
let out = preadv2_safe(file, dst, -1, libc::RWF_NOWAIT);
783+
if let Err(err) = &out {
784+
if matches!(err.raw_os_error(), Some(libc::ENOSYS)) {
785+
NONBLOCKING_READ_SUPPORTED.store(false, Ordering::Relaxed);
786+
return None;
787+
}
788+
}
789+
Some(out)
790+
}
791+
792+
fn preadv2_safe(
793+
file: &crate::fs::sys::File,
794+
dst: &mut ReadBuf<'_>,
795+
offset: off_t,
796+
flags: c_int,
797+
) -> std::io::Result<()> {
798+
unsafe {
799+
/* We're manually have to defend against buffer overflows here. The slice API makes
800+
* this fairly streightforward. */
801+
let unfilled = dst.unfilled_mut();
802+
let iov = iovec {
803+
iov_base: unfilled.as_mut_ptr() as *mut c_void,
804+
iov_len: unfilled.len(),
805+
};
806+
/* We take a File object rather than an fd as reading from a sensitive fd may confuse
807+
* other unsafe code that assumes that only they have access to that fd. */
808+
let bytes_read = preadv2(file.as_raw_fd(), &iov as *const iovec, 1, offset, flags);
809+
if bytes_read < 0 {
810+
Err(std::io::Error::last_os_error())
811+
} else {
812+
/* preadv2 returns the number of bytes read, e.g. the number of bytes that have
813+
* written into `unfilled`. So it's safe to assume that the data is now
814+
* initialised */
815+
dst.assume_init(dst.filled().len() + bytes_read as usize);
816+
dst.advance(bytes_read as usize);
817+
Ok(())
818+
}
819+
}
820+
}
821+
}
822+
823+
#[cfg(any(not(target_os = "linux"), test))]
824+
mod read_nowait {
825+
use crate::io::ReadBuf;
826+
827+
pub(crate) fn try_nonblocking_read(
828+
_file: &crate::fs::sys::File,
829+
_dst: &mut ReadBuf<'_>,
830+
) -> Option<std::io::Result<()>> {
831+
None
832+
}
833+
}

0 commit comments

Comments
 (0)