Skip to content
9 changes: 9 additions & 0 deletions tokio/src/fs/mocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,15 @@ impl From<MockFile> for OwnedFd {
}
}

#[cfg(all(test, unix))]
impl From<OwnedFd> for MockFile {
#[inline]
fn from(file: OwnedFd) -> MockFile {
use std::os::fd::IntoRawFd;
unsafe { MockFile::from_raw_fd(IntoRawFd::into_raw_fd(file)) }
}
}

tokio_thread_local! {
static QUEUE: RefCell<VecDeque<Box<dyn FnOnce() + Send>>> = RefCell::new(VecDeque::new())
}
Expand Down
12 changes: 12 additions & 0 deletions tokio/src/fs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,18 @@ cfg_windows! {

cfg_io_uring! {
pub(crate) mod read_uring;
#[cfg(
// libc::statx is only supported on these platforms
// FIXME: Add musl target env when our minimum supported
// rust version is 1.93. To clarify, statx support is
// introduced to musl in 1.25 as mentioned officially here:
// https://musl.libc.org/releases.html.
// However, rustup target_env building for *-linux-musl
// uses 1.25 musl on all *-linux-musl platforms starting
// in 1.93 stable rust version.
// https://blog.rust-lang.org/2025/12/05/Updating-musl-1.2.5/
any(target_env = "gnu", target_os = "android")
)]
pub(crate) use self::read_uring::read_uring;

pub(crate) use self::open_options::UringOpenOptions;
Expand Down
13 changes: 8 additions & 5 deletions tokio/src/fs/open_options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,10 @@ impl OpenOptions {
/// [`Other`]: std::io::ErrorKind::Other
/// [`PermissionDenied`]: std::io::ErrorKind::PermissionDenied
pub async fn open(&self, path: impl AsRef<Path>) -> io::Result<File> {
self.open_inner(path.as_ref()).await
}

async fn open_inner(&self, path: &Path) -> io::Result<File> {
match &self.inner {
Kind::Std(opts) => Self::std_open(opts, path).await,
#[cfg(all(
Expand All @@ -535,7 +539,7 @@ impl OpenOptions {
.check_and_init(io_uring::opcode::OpenAt::CODE)
.await?
{
Op::open(path.as_ref(), opts)?.await
Op::open(path, opts)?.await
} else {
let opts = opts.clone().into();
Self::std_open(&opts, path).await
Expand All @@ -544,12 +548,11 @@ impl OpenOptions {
}
}

async fn std_open(opts: &StdOpenOptions, path: impl AsRef<Path>) -> io::Result<File> {
let path = path.as_ref().to_owned();
async fn std_open(opts: &StdOpenOptions, path: &Path) -> io::Result<File> {
let path = path.to_owned();
let opts = opts.clone();

let std = asyncify(move || opts.open(path)).await?;
Ok(File::from_std(std))
Ok(asyncify(move || opts.open(path)).await?.into())
}

#[cfg(windows)]
Expand Down
20 changes: 17 additions & 3 deletions tokio/src/fs/read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,23 @@ use std::{io, path::Path};
/// }
/// ```
pub async fn read(path: impl AsRef<Path>) -> io::Result<Vec<u8>> {
let path = path.as_ref().to_owned();
let path = path.as_ref();

#[cfg(all(
tokio_unstable,
feature = "io-uring",
feature = "rt",
feature = "fs",
target_os = "linux"
// libc::statx is only supported on these platforms
// FIXME: Add musl target env when our minimum supported
// rust version is 1.93. To clarify, statx support is
// introduced to musl in 1.25 as mentioned officially here:
// https://musl.libc.org/releases.html.
// However, rustup target_env building for *-linux-musl
// uses 1.25 musl on all *-linux-musl platforms starting
// in 1.93 stable rust version.
// https://blog.rust-lang.org/2025/12/05/Updating-musl-1.2.5/
any(target_env = "gnu", target_os = "android")
))]
{
use crate::fs::read_uring;
Expand All @@ -72,9 +81,14 @@ pub async fn read(path: impl AsRef<Path>) -> io::Result<Vec<u8>> {
.check_and_init(io_uring::opcode::Read::CODE)
.await?
{
return read_uring(&path).await;
return read_uring(path).await;
}
}

read_spawn_blocking(path).await
}

async fn read_spawn_blocking(path: &Path) -> io::Result<Vec<u8>> {
let path = path.to_owned();
asyncify(move || std::fs::read(path)).await
}
28 changes: 26 additions & 2 deletions tokio/src/fs/read_uring.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
#[allow(unused)] // FIXME: remove when MSRV is 1.93 (due to statx on 1.25 musl)
use crate::fs::OpenOptions;
use crate::runtime::driver::op::Op;

use std::io;
use std::io::ErrorKind;
use std::os::fd::OwnedFd;
#[allow(unused)] // FIXME: remove when MSRV is 1.93 (due to statx on 1.25 musl)
use std::path::Path;

// this algorithm is inspired from rust std lib version 1.90.0
Expand All @@ -14,13 +16,32 @@ const PROBE_SIZE_U32: u32 = PROBE_SIZE as u32;
// Max bytes we can read using io uring submission at a time
// SAFETY: cannot be higher than u32::MAX for safe cast
// Set to read max 64 MiB at time
#[allow(unused)] // FIXME: remove when MSRV is 1.93 (due to statx on 1.25 musl)
const MAX_READ_SIZE: usize = 64 * 1024 * 1024;

#[cfg(all(
tokio_unstable,
feature = "io-uring",
feature = "rt",
feature = "fs",
// libc::statx is only supported on these platforms
// FIXME: Add musl target env when our minimum supported
// rust version is 1.93. To clarify, statx support is
// introduced to musl in 1.25 as mentioned officially here:
// https://musl.libc.org/releases.html.
// However, rustup target_env building for *-linux-musl
// uses 1.25 musl on all *-linux-musl platforms starting
// in 1.93 stable rust version.
// https://blog.rust-lang.org/2025/12/05/Updating-musl-1.2.5/
any(target_env = "gnu", target_os = "android")
))]
pub(crate) async fn read_uring(path: &Path) -> io::Result<Vec<u8>> {
let file = OpenOptions::new().read(true).open(path).await?;

// TODO: use io uring in the future to obtain metadata
let size_hint: Option<usize> = file.metadata().await.map(|m| m.len() as usize).ok();
let size_hint = Op::file_metadata(&file)?
.await
.map(|m| m.len() as usize)
.ok();

let fd: OwnedFd = file
.try_into_std()
Expand All @@ -36,6 +57,7 @@ pub(crate) async fn read_uring(path: &Path) -> io::Result<Vec<u8>> {
read_to_end_uring(fd, buf).await
}

#[allow(unused)] // FIXME: remove when MSRV is 1.93 (due to statx on 1.25 musl)
async fn read_to_end_uring(mut fd: OwnedFd, mut buf: Vec<u8>) -> io::Result<Vec<u8>> {
let mut offset = 0;
let start_cap = buf.capacity();
Expand Down Expand Up @@ -80,6 +102,7 @@ async fn read_to_end_uring(mut fd: OwnedFd, mut buf: Vec<u8>) -> io::Result<Vec<
}
}

#[allow(unused)] // FIXME: remove when MSRV is 1.93 (due to statx on 1.25 musl)
async fn small_probe_read(
fd: OwnedFd,
mut buf: Vec<u8>,
Expand Down Expand Up @@ -109,6 +132,7 @@ async fn small_probe_read(
// Takes a length to read and returns a single read in the buffer
//
// Returns the file descriptor, buffer and EOF reached or not
#[allow(unused)] // FIXME: remove when MSRV is 1.93 (due to statx on 1.25 musl)
async fn op_read(
mut fd: OwnedFd,
mut buf: Vec<u8>,
Expand Down
64 changes: 63 additions & 1 deletion tokio/src/fs/try_exists.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,68 @@ use std::path::Path;
/// # }
/// ```
pub async fn try_exists(path: impl AsRef<Path>) -> io::Result<bool> {
let path = path.as_ref().to_owned();
let path = path.as_ref();

#[cfg(all(
tokio_unstable,
feature = "io-uring",
feature = "rt",
feature = "fs",
// libc::statx is only supported on these platforms
// FIXME: Add musl target env when our minimum supported
// rust version is 1.93. To clarify, statx support is
// introduced to musl in 1.25 as mentioned officially here:
// https://musl.libc.org/releases.html.
// However, rustup target_env building for *-linux-musl
// uses 1.25 musl on all *-linux-musl platforms starting
// in 1.93 stable rust version.
// https://blog.rust-lang.org/2025/12/05/Updating-musl-1.2.5/
any(target_env = "gnu", target_os = "android")
))]
{
let handle = crate::runtime::Handle::current();
let driver_handle = handle.inner.driver().io();
if driver_handle
.check_and_init(io_uring::opcode::Statx::CODE)
.await?
{
return try_exists_uring(path).await;
}
}

try_exists_spawn_blocking(path).await
}

cfg_io_uring! {
#[inline]
#[cfg(all(
tokio_unstable,
feature = "io-uring",
feature = "rt",
feature = "fs",
// libc::statx is only supported on these platforms
// FIXME: Add musl target env when our minimum supported
// rust version is 1.93. To clarify, statx support is
// introduced to musl in 1.25 as mentioned officially here:
// https://musl.libc.org/releases.html.
// However, rustup target_env building for *-linux-musl
// uses 1.25 musl on all *-linux-musl platforms starting
// in 1.93 stable rust version.
// https://blog.rust-lang.org/2025/12/05/Updating-musl-1.2.5/
any(target_env = "gnu", target_os = "android")
))]
async fn try_exists_uring(path: &Path) -> io::Result<bool> {
use crate::runtime::driver::op::Op;

match Op::metadata(path)?.await {
Ok(_) => Ok(true),
Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(false),
Err(error) => Err(error),
}
}
}

async fn try_exists_spawn_blocking(path: &Path) -> io::Result<bool> {
let path = path.to_owned();
asyncify(move || path.try_exists()).await
}
69 changes: 32 additions & 37 deletions tokio/src/fs/write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,49 +48,44 @@ pub async fn write(path: impl AsRef<Path>, contents: impl AsRef<[u8]>) -> io::Re
write_spawn_blocking(path, contents).await
}

#[cfg(all(
tokio_unstable,
feature = "io-uring",
feature = "rt",
feature = "fs",
target_os = "linux"
))]
async fn write_uring(path: &Path, mut buf: OwnedBuf) -> io::Result<()> {
use crate::{fs::OpenOptions, runtime::driver::op::Op};
use std::os::fd::OwnedFd;
cfg_io_uring! {
async fn write_uring(path: &Path, mut buf: OwnedBuf) -> io::Result<()> {
use crate::{fs::OpenOptions, runtime::driver::op::Op};
use std::os::fd::OwnedFd;

let file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(path)
.await?;
let file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(path)
.await?;

let mut fd: OwnedFd = file
.try_into_std()
.expect("unexpected in-flight operation detected")
.into();
let mut fd: OwnedFd = file
.try_into_std()
.expect("unexpected in-flight operation detected")
.into();

let total: usize = buf.as_ref().len();
let mut buf_offset: usize = 0;
let mut file_offset: u64 = 0;
while buf_offset < total {
let (res, _buf, _fd) = Op::write_at(fd, buf, buf_offset, file_offset)?.await;
let total: usize = buf.as_ref().len();
let mut buf_offset: usize = 0;
let mut file_offset: u64 = 0;
while buf_offset < total {
let (res, _buf, _fd) = Op::write_at(fd, buf, buf_offset, file_offset)?.await;

let n = match res {
Ok(0) => return Err(io::ErrorKind::WriteZero.into()),
Ok(n) => n,
Err(e) if e.kind() == io::ErrorKind::Interrupted => 0,
Err(e) => return Err(e),
};
let n = match res {
Ok(0) => return Err(io::ErrorKind::WriteZero.into()),
Ok(n) => n,
Err(e) if e.kind() == io::ErrorKind::Interrupted => 0,
Err(e) => return Err(e),
};

buf = _buf;
fd = _fd;
buf_offset += n as usize;
file_offset += n as u64;
}
buf = _buf;
fd = _fd;
buf_offset += n as usize;
file_offset += n as u64;
}

Ok(())
Ok(())
}
}

async fn write_spawn_blocking(path: &Path, contents: OwnedBuf) -> io::Result<()> {
Expand Down
1 change: 1 addition & 0 deletions tokio/src/io/uring/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub(crate) mod open;
pub(crate) mod read;
pub(crate) mod statx;
pub(crate) mod utils;
pub(crate) mod write;
Loading
Loading