Struct glommio::io::DmaFile

source ·
pub struct DmaFile { /* private fields */ }
Expand description

An asynchronously accessed Direct Memory Access (DMA) file.

All access uses Direct I/O, and all operations including open and close are asynchronous (with some exceptions noted). Reads from and writes to this struct must come and go through the DmaBuffer type, which will buffer them in memory; on calling DmaFile::write_at and DmaFile::read_at, the buffers will be passed to the OS to asynchronously write directly to the file on disk, bypassing page caches.

See the module-level documentation for more details and examples.

Implementations§

source§

impl DmaFile

source

pub fn align_up(&self, v: u64) -> u64

align a value up to the minimum alignment needed to access this file

source

pub fn align_down(&self, v: u64) -> u64

align a value down to the minimum alignment needed to access this file

source§

impl DmaFile

source

pub fn is_same(&self, other: &DmaFile) -> bool

Returns true if the DmaFiles represent the same file on the underlying device.

Files are considered to be the same if they live in the same file system and have the same Linux inode. Note that based on this rule a symlink is not considered to be the same file.

Files will be considered to be the same if:

  • A file is opened multiple times (different file descriptors, but same file!)
  • they are hard links.
§Examples
use glommio::{io::DmaFile, LocalExecutor};
use std::os::unix::io::AsRawFd;

let ex = LocalExecutor::default();
ex.run(async {
    let mut wfile = DmaFile::create("myfile.txt").await.unwrap();
    let mut rfile = DmaFile::open("myfile.txt").await.unwrap();
    // Different objects (OS file descriptors), so they will be different...
    assert_ne!(wfile.as_raw_fd(), rfile.as_raw_fd());
    // However they represent the same object.
    assert!(wfile.is_same(&rfile));
    wfile.close().await;
    rfile.close().await;
});
source

pub fn alloc_dma_buffer(&self, size: usize) -> DmaBuffer

Allocates a buffer that is suitable for using to write to this file.

source

pub async fn create<P: AsRef<Path>>(path: P) -> Result<DmaFile, ()>

Similar to create() in the standard library, but returns a DMA file

source

pub async fn open<P: AsRef<Path>>(path: P) -> Result<DmaFile, ()>

Similar to open() in the standard library, but returns a DMA file

source

pub fn dup(&self) -> Result<Self, ()>

Creates a duplicate instance pointing to the same file descriptor as self.

Warning: If the file has been opened with `append`, then the position for writes will get ignored and the buffer will be written at the current end of file. See the [man page] for `O_APPEND`. All dup'ed files will share the same offset (i.e. writes to one will affect the other).

§Examples
use glommio::{
  LocalExecutor,
  io::{
    OpenOptions,
    DmaBuffer,
  }
};

fn populate(buf: &mut DmaBuffer) {
    buf.as_bytes_mut()[0..5].copy_from_slice(b"hello");
}

let ex = LocalExecutor::default();
ex.run(async {
    // A new anonymous file is created within `some_directory/`.
    let file = OpenOptions::new()
      .create_new(true)
      .read(true)
      .write(true)
      .tmpfile(true)
      .dma_open("some_directory")
      .await
      .unwrap();

    let file2 = file.dup().unwrap();

    let mut buf = file.alloc_dma_buffer(4096);
    // Write some data into the buffer.
    populate(&mut buf);

    let written = file.write_at(buf, 0).await.unwrap();
    assert_eq!(written, 4096);
    file.close().await.unwrap();

    let read = file2.read_at_aligned(0, 4096).await.unwrap();
    assert_eq!(read.len(), 4096);
    assert_eq!(&read[0..6], b"hello\0");
});
source

pub async fn write_at(&self, buf: DmaBuffer, pos: u64) -> Result<usize, ()>

Write the buffer in buf to a specific position in the file.

It is expected that the buffer and the position be properly aligned for Direct I/O. In most platforms that means 4096 bytes. There is no write_at_aligned, since a nonaligned write would require a read-modify-write.

Buffers should be allocated through alloc_dma_buffer, which guarantees proper alignment, but alignment on position is still up to the user.

This method acquires ownership of the buffer so the buffer can be kept alive while the kernel has it.

Note that it is legal to return fewer bytes than the buffer size. That is the situation, for example, when the device runs out of space (See the man page for write(2) for details)

Warning: If the file has been opened with `append`, then the position will get ignored and the buffer will be written at the current end of file. See the [man page] for `O_APPEND`

§Examples
use glommio::{io::DmaFile, LocalExecutor};

let ex = LocalExecutor::default();
ex.run(async {
    let file = DmaFile::create("test.txt").await.unwrap();

    let mut buf = file.alloc_dma_buffer(4096);
    let res = file.write_at(buf, 0).await.unwrap();
    assert!(res <= 4096);
    file.close().await.unwrap();
});
source

pub async fn write_rc_at( &self, buf: Rc<DmaBuffer>, pos: u64 ) -> Result<usize, ()>

Equivalent to DmaFile::write_at except that the caller retains non-mutable ownership of the underlying buffer. This can be useful if you want to asynchronously process a page concurrently with writing it.

§Examples
use futures::join;
use glommio::{
    io::{DmaBuffer, DmaFile},
    timer::sleep,
    LocalExecutor,
};
use std::rc::Rc;

fn populate(buf: &mut DmaBuffer) {
    buf.as_bytes_mut()[0..5].copy_from_slice(b"hello");
}

async fn transform(buf: &[u8]) -> Vec<u8> {
    // Dummy implementation that just returns a copy of what was written.
    sleep(std::time::Duration::from_millis(100)).await;
    buf.iter()
        .map(|a| if *a == 0 { 0 } else { *a + 1 })
        .collect()
}

let ex = LocalExecutor::default();
ex.run(async {
    let file = DmaFile::create("test.txt").await.unwrap();

    let mut buf = file.alloc_dma_buffer(4096);
    // Write some data into the buffer.
    populate(&mut buf);

    // Seal the buffer by moving ownership to a non-threaded reference
    // counter on the heap.
    let buf = Rc::new(buf);

    let (written, transformed) = join!(
        async { file.write_rc_at(buf.clone(), 0).await.unwrap() },
        transform(buf.as_bytes())
    );
    assert_eq!(written, 4096);
    file.close().await.unwrap();

    // transformed AND buf can still be used even though the buffer got
    // written. Note that there may be performance issues if buf is large
    // and you remain hanging onto it.
});
source

pub async fn read_at_aligned( &self, pos: u64, size: usize ) -> Result<ReadResult, ()>

Reads from a specific position in the file and returns the buffer.

The position must be aligned to for Direct I/O. In most platforms that means 512 bytes.

source

pub async fn read_at(&self, pos: u64, size: usize) -> Result<ReadResult, ()>

Reads into buffer in buf from a specific position in the file.

It is not necessary to respect the O_DIRECT alignment of the file, and this API will internally convert the positions and sizes to match, at a cost.

If you can guarantee proper alignment, prefer Self::read_at_aligned instead

source

pub fn read_many<V, S>( self: &Rc<DmaFile>, iovs: S, buffer_limit: MergedBufferLimit, read_amp_limit: ReadAmplificationLimit ) -> ReadManyResult<V, impl Stream<Item = (ScheduledSource, ReadManyArgs<V>)>>
where V: IoVec + Unpin, S: Stream<Item = V> + Unpin,

Submit many reads and process the results in a stream-like fashion via a ReadManyResult.

This API will optimistically coalesce and deduplicate IO requests such that two overlapping or adjacent reads will result in a single IO request. This is transparent for the consumer, you will still receive individual ReadResults corresponding to what you asked for.

The first argument is a stream of IoVec. The last two arguments control how aggressive the IO coalescing should be:

  • buffer_limit controls how large a merged IO request can get;
  • read_amp_limit controls how much read amplification is acceptable.

It is not necessary to respect the O_DIRECT alignment of the file, and this API will internally align the reads appropriately.

source

pub async fn copy_file_range_aligned( &self, fd_in: &DmaFile, off_in: u64, len: usize, off_out: u64 ) -> Result<usize, ()>

Copies a file range from one file to another in kernel space. This is going to have the same performance characteristic as splice except if both files are on the same filesystem and the filesystem supports reflinks. In that case, the underlying disk blocks will be CoW linked instead of actually performing a copy. Since copy_file_range is not yet implemented on io_uring (https://github.com/axboe/liburing/issues/831), this is just a dispatch to the blocking thread pool to do the syscall.

source

pub async fn fdatasync(&self) -> Result<(), ()>

Issues fdatasync for the underlying file, instructing the OS to flush all writes to the device, providing durability even if the system crashes or is rebooted.

As this is a DMA file, the OS will not be caching this file; however, there may be caches on the drive itself.

source

pub fn alignment(&self) -> u64

Returns the alignment required for I/O operations. Typical values will be 512 (NVME drive is configured in slower compat mode) or 4096 (typical TLC native alignment).

source

pub async fn deallocate(&self, offset: u64, size: u64) -> Result<(), ()>

Erases a range from the file without changing the size. Check the man page for fallocate for a list of the supported filesystems. Partial blocks are zeroed while whole blocks are simply unmapped from the file. The reported file size (file_size) is unchanged but the allocated file size may if you’ve erased whole filesystem blocks (allocated_file_size)

source

pub async fn pre_allocate(&self, size: u64, keep_size: bool) -> Result<(), ()>

pre-allocates space in the filesystem to hold a file at least as big as the size argument. No existing data in the range [0, size) is modified. If keep_size is false, then anything in [current file length, size) will report zeroed blocks until overwritten and the file size reported will be size. If keep_size is true then the existing file size is unchanged.

source

pub async fn hint_extent_size(&self, size: usize) -> Result<i32, ()>

Hint to the OS the size of increase of this file, to allow more efficient allocation of blocks.

Allocating blocks at the filesystem level turns asynchronous writes into threaded synchronous writes, as we need to first find the blocks to host the file.

If the extent is larger, that means many blocks are allocated at a time. For instance, if the extent size is 1 MiB, that means that only 1 out of 4 256 KiB writes will be turned synchronous. Combined with diligent use of fallocate we can greatly minimize context switches.

It is important not to set the extent size too big. Writes can fail otherwise if the extent can’t be allocated.

source

pub async fn truncate(&self, size: u64) -> Result<(), ()>

Truncates a file to the specified size.

Note: this syscall might be issued in a background thread depending on the system’s capabilities.

source

pub async fn rename<P: AsRef<Path>>(&self, new_path: P) -> Result<(), ()>

Rename this file.

Note: this syscall might be issued in a background thread depending on the system’s capabilities.

source

pub async fn remove(&self) -> Result<(), ()>

Remove this file.

The file does not have to be closed to be removed. Removing removes the name from the filesystem but the file will still be accessible for as long as it is open.

Note: this syscall might be issued in a background thread depending on the system’s capabilities.

source

pub async fn file_size(&self) -> Result<u64, ()>

Returns the size of a file, in bytes

source

pub async fn stat(&self) -> Result<Stat, ()>

Returns the size of the filesystem cluster, in bytes

source

pub async fn close(self) -> Result<(), ()>

Closes this DMA file.

source

pub fn path(&self) -> Option<Ref<'_, Path>>

Returns an Option containing the path associated with this open directory, or None if there isn’t one.

source

pub fn inode(&self) -> u64

The inode backing the file. A file with the same inode may appear under multiple paths due to renaming and linking.

source

pub fn dev_major(&self) -> u32

The major ID of the device containing the filesystem where the file resides. The device may be found by issuing a readlink`` on /sys/dev/block/:`

source

pub fn dev_minor(&self) -> u32

The minor ID of the device containing the filesystem where the file resides.

source

pub async fn close_rc(self: Rc<DmaFile>) -> Result<CloseResult, ()>

Convenience method that closes a DmaFile wrapped inside an Rc.

Returns CloseResult to indicate which operation was performed.

Trait Implementations§

source§

impl AsRawFd for DmaFile

source§

fn as_raw_fd(&self) -> RawFd

Extracts the raw file descriptor. Read more
source§

impl Debug for DmaFile

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl From<DmaFile> for OwnedDmaFile

source§

fn from(value: DmaFile) -> Self

Converts to this type from the input type.
source§

impl From<OwnedDmaFile> for DmaFile

source§

fn from(value: OwnedDmaFile) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

§

impl !Freeze for DmaFile

§

impl !RefUnwindSafe for DmaFile

§

impl !Send for DmaFile

§

impl !Sync for DmaFile

§

impl Unpin for DmaFile

§

impl !UnwindSafe for DmaFile

Blanket Implementations§

source§

impl<T> Any for T
where T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for T
where T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for T
where U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

source§

impl<T> Pointable for T

source§

const ALIGN: usize = _

The alignment of pointer.
§

type Init = T

The type for initializers.
source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more