Skip to main content

rio/
lib.rs

1//! A steamy river of uring. Fast IO using io_uring.
2//!
3//! io_uring is going to change everything. It will speed up your
4//! disk usage by like 300%. Go ahead, run the `O_DIRECT` example
5//! and compare that to using a threadpool or anything
6//! you want. It's not gonna come close!
7//!
8//! Starting in linux 5.5, it also has support for tcp accept.
9//! This is gonna shred everything out there!!!
10//!
11//! But there's a few snags. Mainly, it's a little misuse-prone.
12//! But Rust is pretty nice for specifying proofs about
13//! memory usage in the type system. And we don't even have
14//! to get too squirley. Check out the `write_at` implementation,
15//! for example. It just says that the Completion, the underlying
16//! uring, the file being used, the buffer being used, etc...
17//! will all be in scope at the same time while the Completion
18//! is in-use.
19//!
20//! Most of the other io_uring libraries make
21//! it really easy to blow your legs off with
22//! use-after-frees. `rio` uses standard Rust
23//! lifetime specification  to make most use-after-frees
24//! fail to compile. Also, if a `Completion`
25//! that was pinned to the lifetime of a uring
26//! and backing buffer is dropped, it
27//! waits for its backing operation to complete
28//! before returning from Drop, to further
29//! prevent use-after-frees. use-after-frees
30//! are difficult to express when using `rio`.
31//!
32//! # Examples
33//!
34//! This won't compile:
35//!
36//! ```compile_fail
37//! let rio = rio::new().unwrap();
38//! let file = std::fs::File::open("use_after_free").unwrap();
39//! let out_buf = vec![42; 666];
40//!
41//! let completion = rio.write_at(&file, &out_buf, 0).unwrap();
42//!
43//! // At this very moment, the kernel has a pointer to that there slice.
44//! // It also has the raw file descriptor of the file.
45//! // It's fixin' to write the data from that memory into the file.
46//! // But if we freed it, it would be a bug,
47//! // and the kernel would write potentially scandalous data
48//! // into the file instead.
49//!
50//! // any of the next 3 lines would cause compilation to fail...
51//! drop(out_io_slice);
52//! drop(file);
53//! drop(rio);
54//!
55//! // this is both a Future and a normal blocking promise thing.
56//! // If you're using async, just call `.await` on it instead
57//! // of `.wait()`
58//! completion.wait();
59//!
60//! // now it's safe to drop those things in any order.
61//! ```
62//!
63//!
64//! Really shines with O_DIRECT:
65//!
66//! ```no_run
67//! use std::{
68//!     fs::OpenOptions,
69//!     io::{IoSlice, Result},
70//!     os::unix::fs::OpenOptionsExt,
71//! };
72//!
73//! const CHUNK_SIZE: u64 = 4096 * 256;
74//!
75//! // `O_DIRECT` requires all reads and writes
76//! // to be aligned to the block device's block
77//! // size. 4096 might not be the best, or even
78//! // a valid one, for yours!
79//! #[repr(align(4096))]
80//! struct Aligned([u8; CHUNK_SIZE as usize]);
81//!
82//! fn main() -> Result<()> {
83//!     // start the ring
84//!     let ring = rio::new().expect("create uring");
85//!
86//!     // open output file, with `O_DIRECT` set
87//!     let file = OpenOptions::new()
88//!         .read(true)
89//!         .write(true)
90//!         .create(true)
91//!         .truncate(true)
92//!         .custom_flags(libc::O_DIRECT)
93//!         .open("file")
94//!         .expect("open file");
95//!
96//!     // create output buffer
97//!     let out_buf = Aligned([42; CHUNK_SIZE as usize]);
98//!     let out_slice = out_buf.0.as_ref();
99//!
100//!     let mut completions = vec![];
101//!
102//!     for i in 0..(4 * 1024) {
103//!         let at = i * CHUNK_SIZE;
104//!
105//!         let completion = ring.write_at(
106//!             &file,
107//!             &out_slice,
108//!             at,
109//!         );
110//!         completions.push(completion);
111//!     }
112//!
113//!     for completion in completions.into_iter() {
114//!         completion.wait()?;
115//!     }
116//!
117//!     Ok(())
118//! }
119//! ```
120#![doc(
121    html_logo_url = "https://raw.githubusercontent.com/spacejam/sled/master/art/tree_face_anti-transphobia.png"
122)]
123#![cfg_attr(test, deny(warnings))]
124#![deny(
125    missing_docs,
126    future_incompatible,
127    nonstandard_style,
128    rust_2018_idioms,
129    missing_copy_implementations,
130    trivial_casts,
131    trivial_numeric_casts,
132    unsafe_code,
133    unused_qualifications
134)]
135#![deny(
136    clippy::cast_lossless,
137    clippy::cast_possible_truncation,
138    clippy::cast_possible_wrap,
139    clippy::cast_precision_loss,
140    clippy::cast_sign_loss,
141    clippy::checked_conversions,
142    clippy::decimal_literal_representation,
143    clippy::doc_markdown,
144    clippy::empty_enum,
145    clippy::explicit_into_iter_loop,
146    clippy::explicit_iter_loop,
147    clippy::expl_impl_clone_on_copy,
148    clippy::fallible_impl_from,
149    clippy::filter_map,
150    clippy::filter_map_next,
151    clippy::find_map,
152    clippy::float_arithmetic,
153    clippy::get_unwrap,
154    clippy::if_not_else,
155    clippy::inline_always,
156    clippy::invalid_upcast_comparisons,
157    clippy::items_after_statements,
158    clippy::map_flatten,
159    clippy::match_same_arms,
160    clippy::maybe_infinite_iter,
161    clippy::mem_forget,
162    clippy::missing_const_for_fn,
163    clippy::module_name_repetitions,
164    clippy::multiple_crate_versions,
165    clippy::multiple_inherent_impl,
166    clippy::mut_mut,
167    clippy::needless_borrow,
168    clippy::needless_continue,
169    clippy::needless_pass_by_value,
170    clippy::non_ascii_literal,
171    clippy::option_map_unwrap_or,
172    clippy::option_map_unwrap_or_else,
173    clippy::path_buf_push_overwrite,
174    clippy::print_stdout,
175    clippy::pub_enum_variant_names,
176    clippy::redundant_closure_for_method_calls,
177    clippy::replace_consts,
178    clippy::result_map_unwrap_or_else,
179    clippy::shadow_reuse,
180    clippy::shadow_same,
181    clippy::shadow_unrelated,
182    clippy::single_match_else,
183    clippy::string_add,
184    clippy::string_add_assign,
185    clippy::type_repetition_in_bounds,
186    clippy::unicode_not_nfc,
187    clippy::unimplemented,
188    clippy::unseparated_literal_suffix,
189    clippy::used_underscore_binding,
190    clippy::wildcard_dependencies,
191    clippy::wildcard_enum_match_arm,
192    clippy::wrong_pub_self_convention
193)]
194
195use std::io;
196
197mod completion;
198mod histogram;
199mod lazy;
200mod metrics;
201
202#[cfg(target_os = "linux")]
203mod io_uring;
204
205#[cfg(target_os = "linux")]
206pub use io_uring::{Config, Ordering, Rio, Uring};
207
208pub use completion::Completion;
209
210use {
211    completion::{pair, Filler},
212    histogram::Histogram,
213    lazy::Lazy,
214    metrics::{Measure, M},
215};
216
217/// Create a new IO system.
218pub fn new() -> io::Result<Rio> {
219    Config::default().start()
220}
221
222/// Encompasses various types of IO structures that
223/// can be operated on as if they were a libc::iovec
224pub trait AsIoVec {
225    /// Returns the address of this object.
226    fn into_new_iovec(&self) -> libc::iovec;
227}
228
229impl<A: ?Sized + AsRef<[u8]>> AsIoVec for A {
230    fn into_new_iovec(&self) -> libc::iovec {
231        let self_ref: &[u8] = self.as_ref();
232        let self_ptr: *const [u8] = self_ref;
233        libc::iovec {
234            iov_base: self_ptr as *mut _,
235            iov_len: self_ref.len(),
236        }
237    }
238}
239
240/// We use this internally as a way of communicating
241/// that for certain operations, we cannot accept a
242/// reference into read-only memory, like for reads.
243///
244/// If your compilation fails because of something
245/// related to this, it's because you are trying
246/// to use memory as a destination for a read
247/// that could never actually be written to anyway,
248/// which the compiler may place in read-only
249/// memory in your process that cannot be written
250/// to by anybody.
251///
252/// # Examples
253///
254/// This will cause the following code to break,
255/// which would have caused an IO error anyway
256/// due to trying to write to static read-only
257/// memory:
258///
259/// ```compile_fail
260/// let ring = rio::new().unwrap();
261/// let file = std::fs::File::open("failure").unwrap();
262///
263/// // the following buffer is placed in
264/// // static, read-only memory and would
265/// // never be valid to write to
266/// let buffer: &[u8] = b"this is read-only";
267///
268/// // this fails to compile, because &[u8]
269/// // does not implement `AsIoVecMut`:
270/// ring.read_at(&file, &buffer, 0).unwrap();
271/// ```
272///
273/// which can be fixed by making it a mutable
274/// slice:
275///
276/// ```no_run
277/// let ring = rio::new().unwrap();
278/// let file = std::fs::File::open("failure").unwrap();
279///
280/// // the following buffer is placed in
281/// // readable and writable memory, due to
282/// // its mutability
283/// let buffer: &mut [u8] = &mut [0; 42];
284///
285/// // this now works
286/// ring.read_at(&file, &buffer, 0).wait();
287/// ```
288pub trait AsIoVecMut {}
289
290impl<A: ?Sized + AsMut<[u8]>> AsIoVecMut for A {}
291
292/// A trait for describing transformations from the
293/// `io_uring_cqe` type into an expected meaningful
294/// high-level result.
295pub trait FromCqe {
296    /// Describes a conversion from a successful
297    /// `io_uring_cqe` to a desired output type.
298    fn from_cqe(cqe: io_uring::io_uring_cqe) -> Self;
299}
300
301impl FromCqe for usize {
302    fn from_cqe(cqe: io_uring::io_uring_cqe) -> usize {
303        use std::convert::TryFrom;
304        usize::try_from(cqe.res).unwrap()
305    }
306}
307
308impl FromCqe for () {
309    fn from_cqe(_: io_uring::io_uring_cqe) {}
310}