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}