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}