seekable_async_file/file.rs
1use crate::common::ISyncIO;
2use std::os::unix::prelude::FileExt;
3use std::path::Path;
4use tokio::fs::OpenOptions;
5
6/// A `File`-like value that can perform async `read_at` and `write_at` for I/O at specific offsets without mutating any state (i.e. is thread safe). Metrics are collected, and syncs can be delayed for write batching opportunities as a performance optimisation.
7pub struct FileIO {
8 // Tokio has still not implemented read_at and write_at: https://github.com/tokio-rs/tokio/issues/1529. We need these to be able to share a file descriptor across threads (e.g. use from within async function).
9 // Apparently spawn_blocking is how Tokio does all file operations (as not all platforms have native async I/O), so our use is not worse but not optimised for async I/O either.
10 fd: std::fs::File,
11}
12
13impl FileIO {
14 /// Open a file descriptor in read and write modes, creating it if it doesn't exist. If it already exists, the contents won't be truncated.
15 ///
16 /// If the mmap feature is being used, to save a `stat` call, the size must be provided. This also allows opening non-standard files which may have a size of zero (e.g. block devices). A different size value also allows only using a portion of the beginning of the file.
17 ///
18 /// The `io_direct` and `io_dsync` parameters set the `O_DIRECT` and `O_DSYNC` flags, respectively. Unless you need those flags, provide `false`.
19 ///
20 /// Make sure to execute `start_delayed_data_sync_background_loop` in the background after this call.
21 pub async fn open(path: &Path, flags: i32) -> Self {
22 let async_fd = OpenOptions::new()
23 .read(true)
24 .write(true)
25 .custom_flags(flags)
26 .open(path)
27 .await
28 .unwrap();
29
30 let fd = async_fd.into_std().await;
31
32 FileIO { fd }
33 }
34}
35
36impl ISyncIO for FileIO {
37 fn read_at_sync(&self, offset: u64, len: u64) -> Vec<u8> {
38 let mut buf = vec![0u8; len.try_into().unwrap()];
39 self.fd.read_exact_at(&mut buf, offset).unwrap();
40 buf
41 }
42
43 fn write_at_sync(&self, offset: u64, data: &[u8]) -> () {
44 self.fd.write_all_at(data, offset).unwrap();
45 }
46
47 fn sync_data_sync(&self) -> () {
48 self.fd.sync_data().unwrap();
49 }
50}