uring_file/
lib.rs

1//! Async file I/O via Linux io_uring.
2//!
3//! This crate provides a high-level, async-compatible interface to Linux's io_uring for file operations. It is designed for high-performance scenarios where traditional threaded I/O or epoll-based async I/O is a bottleneck.
4//!
5//! # Features
6//!
7//! - **Async/await compatible**: Works with tokio and other async runtimes
8//! - **Zero-copy where possible**: Buffers are passed through, not copied
9//! - **Batched submissions**: Multiple I/O operations are batched for efficiency
10//! - **Thread-safe**: `Uring` handles can be shared across threads and tasks
11//!
12//! # Quick Start
13//!
14//! ```ignore
15//! use uring_file::{UringFile, uring::{Uring, UringCfg}};
16//! use std::fs::File;
17//!
18//! #[tokio::main]
19//! async fn main() -> std::io::Result<()> {
20//!     // Option 1: Use the UringFile trait with the global ring
21//!     let file = File::open("data.bin")?;
22//!     let result = file.ur_read_at(0, 4096).await?;
23//!     println!("Read {} bytes", result.bytes_read);
24//!
25//!     // Option 2: Create your own ring for more control
26//!     let uring = Uring::new(UringCfg::default())?;
27//!     let result = uring.read_at(&file, 0, 4096).await?;
28//!     println!("Read {} bytes", result.bytes_read);
29//!
30//!     Ok(())
31//! }
32//! ```
33//!
34//! # Architecture
35//!
36//! The crate uses a shared io_uring instance with dedicated threads:
37//!
38//! ```text
39//! ┌─────────────┐     ┌───────────────────┐     ┌─────────────┐
40//! │ Async Tasks │────▶│ Submission Thread │────▶│  io_uring   │
41//! └─────────────┘     └───────────────────┘     └──────┬──────┘
42//!        ▲                                             │
43//!        │            ┌───────────────────┐            │
44//!        └────────────│ Completion Thread │◀───────────┘
45//!                     └───────────────────┘
46//! ```
47//!
48//! # Kernel Requirements
49//!
50//! - **Minimum**: Linux 5.1 (basic io_uring support)
51//! - **Recommended**: Linux 5.6+ (for statx, fallocate, fadvise)
52//! - **ftruncate**: Linux 6.9+
53//!
54//! # Platform Support
55//!
56//! This crate only compiles on Linux. On other platforms, it will fail at compile time.
57
58#![cfg(target_os = "linux")]
59#![allow(async_fn_in_trait)]
60
61pub mod fs;
62pub mod metadata;
63pub mod uring;
64
65use metadata::Metadata;
66use once_cell::sync::Lazy;
67use std::fs::File as StdFile;
68use std::io;
69use std::os::fd::AsRawFd;
70use tokio::fs::File as TokioFile;
71use uring::ReadResult;
72use uring::Uring;
73use uring::UringCfg;
74use uring::WriteResult;
75
76// Re-export for convenience
77pub use uring::DEFAULT_RING_SIZE;
78
79// ============================================================================
80// Global Default Ring
81// ============================================================================
82
83/// Global default io_uring instance. Lazily initialized on first use with default configuration. For production use with specific requirements, consider creating your own `Uring` instance with custom configuration.
84static DEFAULT_URING: Lazy<Uring> =
85  Lazy::new(|| Uring::new(UringCfg::default()).expect("failed to initialize io_uring"));
86
87/// Get a reference to the global default io_uring instance.
88pub fn default_uring() -> &'static Uring {
89  &DEFAULT_URING
90}
91
92// ============================================================================
93// UringFile Trait
94// ============================================================================
95
96/// Extension trait for performing io_uring operations on file types.
97///
98/// This trait is implemented for `std::fs::File` and `tokio::fs::File`, allowing you to call io_uring operations directly on file handles. All operations use the global default io_uring instance. For more control, use the `Uring` struct directly.
99pub trait UringFile: AsRawFd {
100  /// Read from the file at the specified offset. Returns the buffer and actual bytes read. The buffer may contain fewer bytes than requested if EOF is reached.
101  async fn ur_read_at(&self, offset: u64, len: u64) -> io::Result<ReadResult<Vec<u8>>>;
102
103  /// Read exactly `len` bytes from the file at the specified offset. Returns an error if fewer bytes are available.
104  async fn ur_read_exact_at(&self, offset: u64, len: u64) -> io::Result<Vec<u8>>;
105
106  /// Write to the file at the specified offset. Returns the original buffer and bytes written.
107  async fn ur_write_at(&self, offset: u64, data: Vec<u8>) -> io::Result<WriteResult<Vec<u8>>>;
108
109  /// Write all bytes to the file at the specified offset. Loops on short writes until complete.
110  async fn ur_write_all_at(&self, offset: u64, data: &[u8]) -> io::Result<()>;
111
112  /// Synchronize file data and metadata to disk (fsync).
113  async fn ur_sync(&self) -> io::Result<()>;
114
115  /// Synchronize file data to disk (fdatasync). Faster than fsync as it doesn't sync metadata unless required.
116  async fn ur_datasync(&self) -> io::Result<()>;
117
118  /// Get file metadata via statx. Requires Linux 5.6+.
119  async fn ur_statx(&self) -> io::Result<Metadata>;
120
121  /// Pre-allocate or manipulate file space. Requires Linux 5.6+.
122  async fn ur_fallocate(&self, offset: u64, len: u64, mode: i32) -> io::Result<()>;
123
124  /// Advise the kernel about file access patterns. Requires Linux 5.6+.
125  async fn ur_fadvise(&self, offset: u64, len: u64, advice: i32) -> io::Result<()>;
126
127  /// Truncate the file to the specified length. Requires Linux 6.9+.
128  async fn ur_ftruncate(&self, len: u64) -> io::Result<()>;
129}
130
131impl UringFile for StdFile {
132  async fn ur_read_at(&self, offset: u64, len: u64) -> io::Result<ReadResult<Vec<u8>>> {
133    DEFAULT_URING.read_at(self, offset, len).await
134  }
135
136  async fn ur_read_exact_at(&self, offset: u64, len: u64) -> io::Result<Vec<u8>> {
137    DEFAULT_URING.read_exact_at(self, offset, len).await
138  }
139
140  async fn ur_write_at(&self, offset: u64, data: Vec<u8>) -> io::Result<WriteResult<Vec<u8>>> {
141    DEFAULT_URING.write_at(self, offset, data).await
142  }
143
144  async fn ur_write_all_at(&self, offset: u64, data: &[u8]) -> io::Result<()> {
145    DEFAULT_URING.write_all_at(self, offset, data).await
146  }
147
148  async fn ur_sync(&self) -> io::Result<()> {
149    DEFAULT_URING.sync(self).await
150  }
151
152  async fn ur_datasync(&self) -> io::Result<()> {
153    DEFAULT_URING.datasync(self).await
154  }
155
156  async fn ur_statx(&self) -> io::Result<Metadata> {
157    DEFAULT_URING.statx(self).await
158  }
159
160  async fn ur_fallocate(&self, offset: u64, len: u64, mode: i32) -> io::Result<()> {
161    DEFAULT_URING.fallocate(self, offset, len, mode).await
162  }
163
164  async fn ur_fadvise(&self, offset: u64, len: u64, advice: i32) -> io::Result<()> {
165    DEFAULT_URING.fadvise(self, offset, len, advice).await
166  }
167
168  async fn ur_ftruncate(&self, len: u64) -> io::Result<()> {
169    DEFAULT_URING.ftruncate(self, len).await
170  }
171}
172
173impl UringFile for TokioFile {
174  async fn ur_read_at(&self, offset: u64, len: u64) -> io::Result<ReadResult<Vec<u8>>> {
175    DEFAULT_URING.read_at(self, offset, len).await
176  }
177
178  async fn ur_read_exact_at(&self, offset: u64, len: u64) -> io::Result<Vec<u8>> {
179    DEFAULT_URING.read_exact_at(self, offset, len).await
180  }
181
182  async fn ur_write_at(&self, offset: u64, data: Vec<u8>) -> io::Result<WriteResult<Vec<u8>>> {
183    DEFAULT_URING.write_at(self, offset, data).await
184  }
185
186  async fn ur_write_all_at(&self, offset: u64, data: &[u8]) -> io::Result<()> {
187    DEFAULT_URING.write_all_at(self, offset, data).await
188  }
189
190  async fn ur_sync(&self) -> io::Result<()> {
191    DEFAULT_URING.sync(self).await
192  }
193
194  async fn ur_datasync(&self) -> io::Result<()> {
195    DEFAULT_URING.datasync(self).await
196  }
197
198  async fn ur_statx(&self) -> io::Result<Metadata> {
199    DEFAULT_URING.statx(self).await
200  }
201
202  async fn ur_fallocate(&self, offset: u64, len: u64, mode: i32) -> io::Result<()> {
203    DEFAULT_URING.fallocate(self, offset, len, mode).await
204  }
205
206  async fn ur_fadvise(&self, offset: u64, len: u64, advice: i32) -> io::Result<()> {
207    DEFAULT_URING.fadvise(self, offset, len, advice).await
208  }
209
210  async fn ur_ftruncate(&self, len: u64) -> io::Result<()> {
211    DEFAULT_URING.ftruncate(self, len).await
212  }
213}