cros_async: Don't use io_uring on kernels < 5.10

Kernels before 5.10 had known bugs in the io_uring implementation.
Don't use io_uring when we detect this.  Also skip all the io_uring
tests in this case.

BUG=none
TEST=cargo test

Change-Id: I5fd6203ad25a6fb85ff28f1a6ddb0181f836ad89
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/3006309
Tested-by: kokoro <noreply+kokoro@google.com>
Commit-Queue: Chirantan Ekbote <chirantan@chromium.org>
Reviewed-by: Keiichi Watanabe <keiichiw@chromium.org>
Reviewed-by: Woody Chow <woodychow@google.com>
This commit is contained in:
Chirantan Ekbote 2021-07-06 16:35:07 +09:00 committed by Commit Bot
parent 313f5133dc
commit 6074d45aeb
14 changed files with 121 additions and 49 deletions

1
Cargo.lock generated
View file

@ -194,6 +194,7 @@ dependencies = [
"intrusive-collections",
"io_uring",
"libc",
"once_cell",
"paste",
"pin-utils",
"slab",

View file

@ -7,15 +7,16 @@ edition = "2018"
[dependencies]
async-trait = "0.1.36"
async-task = "4"
data_model = { path = "../data_model" } # provided by ebuild
intrusive-collections = "0.9"
io_uring = { path = "../io_uring" } # provided by ebuild
libc = "*"
once_cell = "1.7.2"
paste = "1.0"
pin-utils = "0.1.0-alpha.4"
slab = "0.4"
sync = { path = "../sync" } # provided by ebuild
sys_util = { path = "../sys_util" } # provided by ebuild
data_model = { path = "../data_model" } # provided by ebuild
thiserror = "1.0.20"
[dependencies.futures]

View file

@ -52,13 +52,14 @@
//! ensure it lives long enough.
use std::convert::TryInto;
use std::ffi::CStr;
use std::fs::File;
use std::future::Future;
use std::io;
use std::mem;
use std::mem::{self, MaybeUninit};
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
use std::pin::Pin;
use std::sync::atomic::{AtomicI32, AtomicU32, Ordering};
use std::sync::atomic::{AtomicI32, Ordering};
use std::sync::{Arc, Weak};
use std::task::Waker;
use std::task::{Context, Poll};
@ -67,6 +68,7 @@ use std::thread::{self, ThreadId};
use async_task::Task;
use futures::task::noop_waker;
use io_uring::URingContext;
use once_cell::sync::Lazy;
use pin_utils::pin_mut;
use slab::Slab;
use sync::Mutex;
@ -130,29 +132,41 @@ impl From<Error> for io::Error {
}
}
static USE_URING: Lazy<bool> = Lazy::new(|| {
let mut utsname = MaybeUninit::zeroed();
// Safe because this will only modify `utsname` and we check the return value.
let res = unsafe { libc::uname(utsname.as_mut_ptr()) };
if res < 0 {
return false;
}
// Safe because the kernel has initialized `utsname`.
let utsname = unsafe { utsname.assume_init() };
// Safe because the pointer is valid and the kernel guarantees that this is a valid C string.
let release = unsafe { CStr::from_ptr(utsname.release.as_ptr()) };
let mut components = match release.to_str().map(|r| r.split('.').map(str::parse)) {
Ok(c) => c,
Err(_) => return false,
};
// Kernels older than 5.10 either didn't support io_uring or had bugs in the implementation.
match (components.next(), components.next()) {
(Some(Ok(major)), Some(Ok(minor))) if (major, minor) >= (5, 10) => {
// The kernel version is new enough so check if we can actually make a uring context.
URingContext::new(8).is_ok()
}
_ => false,
}
});
// Checks if the uring executor is available.
// Caches the result so that the check is only run once.
// Useful for falling back to the FD executor on pre-uring kernels.
pub(crate) fn use_uring() -> bool {
const UNKNOWN: u32 = 0;
const URING: u32 = 1;
const FD: u32 = 2;
static USE_URING: AtomicU32 = AtomicU32::new(UNKNOWN);
match USE_URING.load(Ordering::Relaxed) {
UNKNOWN => {
// Create a dummy uring context to check that the kernel understands the syscalls.
if URingContext::new(8).is_ok() {
USE_URING.store(URING, Ordering::Relaxed);
true
} else {
USE_URING.store(FD, Ordering::Relaxed);
false
}
}
URING => true,
FD => false,
_ => unreachable!("invalid use uring state"),
}
*USE_URING
}
pub struct RegisteredSource {
@ -880,6 +894,10 @@ mod tests {
#[test]
fn dont_drop_backing_mem_read() {
if !use_uring() {
return;
}
// Create a backing memory wrapped in an Arc and check that the drop isn't called while the
// op is pending.
let bm =
@ -920,6 +938,10 @@ mod tests {
#[test]
fn dont_drop_backing_mem_write() {
if !use_uring() {
return;
}
// Create a backing memory wrapped in an Arc and check that the drop isn't called while the
// op is pending.
let bm =
@ -961,6 +983,10 @@ mod tests {
#[test]
fn canceled_before_completion() {
if !use_uring() {
return;
}
async fn cancel_io(op: PendingOperation) {
mem::drop(op);
}
@ -999,6 +1025,10 @@ mod tests {
#[test]
fn drop_before_completion() {
if !use_uring() {
return;
}
const VALUE: u64 = 0xef6c_a8df_b842_eb9c;
async fn check_op(op: PendingOperation) {
@ -1044,6 +1074,10 @@ mod tests {
#[test]
fn drop_on_different_thread() {
if !use_uring() {
return;
}
let ex = URingExecutor::new().unwrap();
let ex2 = ex.clone();

View file

@ -18,26 +18,6 @@ use crate::AsyncResult;
/// `UringSource` wraps FD backed IO sources for use with io_uring. It is a thin wrapper around
/// registering an IO source with the uring that provides an `IoSource` implementation.
/// Most useful functions are provided by 'IoSourceExt'.
///
/// # Example
/// ```rust
/// use std::fs::File;
/// use cros_async::{UringSource, ReadAsync, URingExecutor};
///
/// async fn read_four_bytes(source: &UringSource<File>) -> (usize, Vec<u8>) {
/// let mem = vec![0u8; 4];
/// source.read_to_vec(0, mem).await.unwrap()
/// }
///
/// fn read_file(f: File) -> Result<(), Box<dyn std::error::Error>> {
/// let ex = URingExecutor::new()?;
/// let async_source = UringSource::new(f, &ex)?;
/// let (nread, vec) = ex.run_until(read_four_bytes(&async_source))?;
/// assert_eq!(nread, 4);
/// assert_eq!(vec.len(), 4);
/// Ok(())
/// }
/// ```
pub struct UringSource<F: AsRawFd> {
registered_source: RegisteredSource,
source: F,
@ -236,12 +216,17 @@ mod tests {
use std::path::PathBuf;
use crate::io_ext::{ReadAsync, WriteAsync};
use crate::uring_executor::use_uring;
use crate::UringSource;
use super::*;
#[test]
fn read_to_mem() {
if !use_uring() {
return;
}
use crate::mem::VecIoWrapper;
use std::io::Write;
use tempfile::tempfile;
@ -275,6 +260,10 @@ mod tests {
#[test]
fn readvec() {
if !use_uring() {
return;
}
async fn go(ex: &URingExecutor) {
let f = File::open("/dev/zero").unwrap();
let source = UringSource::new(f, ex).unwrap();
@ -293,6 +282,10 @@ mod tests {
#[test]
fn readmulti() {
if !use_uring() {
return;
}
async fn go(ex: &URingExecutor) {
let f = File::open("/dev/zero").unwrap();
let source = UringSource::new(f, ex).unwrap();
@ -321,6 +314,10 @@ mod tests {
#[test]
fn u64_from_file() {
if !use_uring() {
return;
}
let f = File::open("/dev/zero").unwrap();
let ex = URingExecutor::new().unwrap();
let source = UringSource::new(f, &ex).unwrap();
@ -330,6 +327,10 @@ mod tests {
#[test]
fn event() {
if !use_uring() {
return;
}
use sys_util::EventFd;
async fn write_event(ev: EventFd, wait: EventFd, ex: &URingExecutor) {
@ -367,6 +368,10 @@ mod tests {
#[test]
fn pend_on_pipe() {
if !use_uring() {
return;
}
use std::io::Write;
use futures::future::Either;
@ -393,6 +398,10 @@ mod tests {
#[test]
fn readmem() {
if !use_uring() {
return;
}
async fn go(ex: &URingExecutor) {
let f = File::open("/dev/zero").unwrap();
let source = UringSource::new(f, ex).unwrap();
@ -443,6 +452,10 @@ mod tests {
#[test]
fn range_error() {
if !use_uring() {
return;
}
async fn go(ex: &URingExecutor) {
let f = File::open("/dev/zero").unwrap();
let source = UringSource::new(f, ex).unwrap();
@ -467,6 +480,10 @@ mod tests {
#[test]
fn fallocate() {
if !use_uring() {
return;
}
async fn go(ex: &URingExecutor) {
let dir = tempfile::TempDir::new().unwrap();
let mut file_path = PathBuf::from(dir.path());
@ -500,6 +517,10 @@ mod tests {
#[test]
fn fsync() {
if !use_uring() {
return;
}
async fn go(ex: &URingExecutor) {
let f = tempfile::tempfile().unwrap();
let source = UringSource::new(f, ex).unwrap();
@ -512,6 +533,10 @@ mod tests {
#[test]
fn wait_read() {
if !use_uring() {
return;
}
async fn go(ex: &URingExecutor) {
let f = File::open("/dev/zero").unwrap();
let source = UringSource::new(f, ex).unwrap();
@ -524,6 +549,10 @@ mod tests {
#[test]
fn writemem() {
if !use_uring() {
return;
}
async fn go(ex: &URingExecutor) {
let f = OpenOptions::new()
.create(true)
@ -546,6 +575,10 @@ mod tests {
#[test]
fn writevec() {
if !use_uring() {
return;
}
async fn go(ex: &URingExecutor) {
let f = OpenOptions::new()
.create(true)
@ -567,6 +600,10 @@ mod tests {
#[test]
fn writemulti() {
if !use_uring() {
return;
}
async fn go(ex: &URingExecutor) {
let f = OpenOptions::new()
.create(true)

View file

@ -44,3 +44,4 @@ sigaltstack: 1
write: 1
writev: 1
fcntl: 1
uname: 1

View file

@ -42,6 +42,7 @@ set_robust_list: 1
sigaltstack: 1
write: 1
writev: 1
uname: 1
# Required for perfetto tracing
getsockopt: 1
@ -77,7 +78,6 @@ tgkill: 1
clock_gettime: 1
# Rules specific to Mesa.
uname: 1
sched_setscheduler: 1
sched_setaffinity: 1
kcmp: 1

View file

@ -17,7 +17,6 @@ openat: 1
setsockopt: 1
bind: 1
socket: arg0 == AF_NETLINK
uname: 1
# The following ioctls are:
# 0x4004550d == USBDEVFS_REAPURBNDELAY32
# 0x550b == USBDEVFS_DISCARDURB

View file

@ -53,3 +53,4 @@ sigaltstack: 1
write: 1
writev: 1
fcntl64: 1
uname: 1

View file

@ -48,6 +48,7 @@ set_robust_list: 1
sigaltstack: 1
write: 1
writev: 1
uname: 1
# Required for perfetto tracing
getsockopt: 1
@ -89,7 +90,6 @@ clock_gettime: 1
clock_gettime64: 1
# Rules specific to Mesa.
uname: 1
sched_setscheduler: 1
sched_setaffinity: 1
kcmp: 1

View file

@ -19,7 +19,6 @@ bind: 1
socket: arg0 == AF_NETLINK
stat: 1
statx: 1
uname: 1
# The following ioctls are:
# 0x4004550d == USBDEVFS_REAPURBNDELAY32
# 0x550b == USBDEVFS_DISCARDURB

View file

@ -47,3 +47,4 @@ sigaltstack: 1
write: 1
writev: 1
fcntl: 1
uname: 1

View file

@ -45,6 +45,7 @@ set_robust_list: 1
sigaltstack: 1
write: 1
writev: 1
uname: 1
# Rules specific to gpu
connect: 1
@ -90,7 +91,6 @@ setpriority: 1
unlink: 1
# Rules specific to AMD gpus.
uname: 1
sched_setscheduler: 1
sched_setaffinity: 1
kcmp: 1

View file

@ -35,7 +35,6 @@ mprotect: arg2 == PROT_READ|PROT_WRITE || arg2 == PROT_NONE || arg2 == PROT_READ
readlink: 1
sched_setaffinity: 1
sched_setscheduler: arg1 == SCHED_IDLE || arg1 == SCHED_BATCH
uname: 1
# Required by mesa on AMD GPU
sysinfo: 1

View file

@ -18,7 +18,6 @@ open: return ENOENT
openat: 1
socket: arg0 == AF_NETLINK
stat: 1
uname: 1
# The following ioctls are:
# 0x4008550d == USBDEVFS_REAPURBNDELAY
# 0x41045508 == USBDEVFS_GETDRIVER