Expand description
mfio
Framework for Async I/O Systems
mfio’s mission is to provide building blocks for efficient I/O systems, going beyond typical OS APIs. Originally built for memflow, it aims to make the following aspects of an I/O chain as simple as possible:
- Async
- Automatic batching (vectoring)
- Fragmentation
- Partial success
- Lack of color (full sync support)
- I/O directly to the stack
- Using without standard library
This crate provides core, mostly unopiniated, building blocks for async I/O systems. The biggest design assumption is that this crate is to be used for thread-per-core-like I/O systems.
One could view mfio as programmable I/O, because native fragmentation support allows one to map non-linear I/O space into a linear space. This is incredibly useful for interpretation of process virtual address space on top of physical address space. Async operation allows to queue up multiple I/O operations simultaneously and have them automatically batched up by the I/O implementation. This results in the highest performance possible in scenarios where dispatching a single I/O operation incurs heavy latency. Batching queues up operations and issues fewer calls for the same amount of I/O. Partial success is critical in fragmentable context. Unlike typical I/O interfaces, mfio does not enforce sequence of operations. A single packet may get partially read/written, depending on which parts of the underlying I/O space is available. This works really well with sparse files, albeit differs from the typical “stop as soon as an error occurs” model.
Lack of color is not true sync/async mix, instead, mfio is designed to expose minimal set of data for invoking a built-in runtime, with handles of daisy chaining mfio on top of another runtime. The end result is that mfio is able to provide sync wrappers that efficiently poll async operations to completion, while staying runtime agnostic. We found that a single (unix) file descriptor or (windows) handle is sufficient to connect multiple async runtimes together.
Examples
Read primitive values:
use core::mem::MaybeUninit;
use futures::{Stream, StreamExt};
use mfio::backend::*;
use mfio::io::{PacketIo, Write};
use mfio::traits::*;
let handle = SampleIo::new(vec![0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144]);
// mfio includes a lightweight executor
handle.block_on(async {
// Read a single byte
let byte = handle.read::<u8>(3).await?;
assert_eq!(2, byte);
// Read an integer
let int = handle.read::<u32>(0).await?;
assert_eq!(u32::from_ne_bytes([0, 1, 1, 2]), int);
Ok(())
})
Read primitive values synchronously:
use core::mem::MaybeUninit;
use futures::{Stream, StreamExt};
use mfio::backend::*;
use mfio::io::{PacketIo, Write};
use mfio::traits::sync::*;
let handle = SampleIo::new(vec![0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144]);
// Read a single byte
let byte = handle.read::<u8>(3)?;
assert_eq!(2, byte);
// Read an integer
let int = handle.read::<u32>(0)?;
assert_eq!(u32::from_ne_bytes([0, 1, 1, 2]), int);
Read structures:
use bytemuck::{Pod, Zeroable};
use core::mem::MaybeUninit;
use futures::{pin_mut, Stream, StreamExt};
use mfio::backend::*;
use mfio::io::{PacketIo, Write};
use mfio::traits::*;
#[repr(C, packed)]
#[derive(Eq, PartialEq, Default, Pod, Zeroable, Clone, Copy, Debug)]
struct Sample {
first: usize,
second: u32,
third: u8,
}
let sample = Sample {
second: 42,
..Default::default()
};
let mut handle = SampleIo::new(bytemuck::bytes_of(&sample).into());
// mfio objects can also be plugged into existing executor.
// `Null` is compatible with every executor, but waking must be done externally.
// There is `Tokio`, compatible with tokio runtime.
// There is also `AsyncIo` - for smol, async-std and friends.
Null::run_with_mut(&mut handle, |handle| async move {
// Read value
let val = handle.read(0).await?;
assert_eq!(sample, val);
Ok(())
})
.await
Safety
By default mfio is conservative and does not enable invoking undefined behavior. However, with
a custom opt-in config switch, enabled by passing --cfg mfio_assume_linear_types
to the rust
compiler, mfio is able to provide significant performance improvements, at the cost of
potential for invoking UB in safe code*.
With mfio_assume_linear_types
config enabled, mfio wrappers will prefer storing data on the
stack, and if a future waiting for I/O operations to complete is cancelled, a panic!
may get
triggered. Moreover, if a future waiting for I/O operations to complete gets forgotten using
mem::forget
, undefined behavior may be invoked, because use-after-(stack)-free safeguards are
discarded.
*NOTE: Pin<P>
includes a
drop guarantee, making this
claim technically invalid. In the future releases of mfio
, the config switch will be removed
and most I/O will be done through stack (see https://github.com/memflow/mfio/issues/2).
Re-exports
pub use mfio_derive as derive;
pub use parking_lot as locks;
pub use tarc;
Modules
- Backends for
mfio
. - mfio’s error types
- Provides compatibility with
futures
traits. - Desribes abstract I/O operations.
std::io
equivalent Read/Write traits.- Helper traits
Macros
- Shorthand for building mfio error structure.
- Implements
Read
+Write
+Seek
traits on compatible type.