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}