# lio-uring
A production-ready, safe, and ergonomic Rust interface to Linux's io_uring asynchronous I/O framework.
## Features
- **Safe API**: Minimal unsafe code with clear safety requirements
- **Zero overhead**: Thin wrapper around liburing with no runtime cost
- **Complete**: Support for all major io_uring operations
- **Production-ready**: Comprehensive error handling and documentation
- **Advanced features**:
- Submission queue polling (SQPOLL)
- IO polling (IOPOLL)
- Linked operations
- Fixed files and buffers
- Batch submissions
- Non-blocking completions
## Requirements
- Linux kernel 5.1+ (5.19+ recommended for full feature support)
- liburing is built from source during compilation
## Core Concepts
### Split API
The io_uring instance is split into two handles:
- **SubmissionQueue**: For submitting operations to the submission queue
- **CompletionQueue**: For retrieving completions from the completion queue
This design prevents accidental concurrent access and allows each half to be used from separate threads safely.
### Operations
All io_uring operations are defined in the `operation` module:
```rust
use lio_uring::operation::*;
use std::fs::File;
use std::os::fd::AsRawFd;
let (mut cq, mut sq) = lio_uring::with_capacity(128)?;
let file = File::create("/tmp/test")?;
let data = b"Hello, world!";
let write_op = Write {
fd: file.as_raw_fd(),
ptr: data.as_ptr() as *mut _,
len: data.len() as u32,
offset: 0,
};
unsafe { sq.push(&write_op, 1) }?;
```
### Safety
Operations contain raw pointers and file descriptors. When submitting, you must ensure:
1. All pointers remain valid until the operation completes
2. Buffers are not accessed mutably while operations are in flight
3. File descriptors remain valid until operations complete
### Error Handling
All operations return `io::Result` with proper error propagation:
```rust
let completion = cq.next()?;
match completion.result() {
Ok(bytes) => println!("Transferred {} bytes", bytes),
Err(e) => eprintln!("Operation failed: {}", e),
}
```
## Advanced Features
### Linked Operations
Chain operations to execute sequentially:
```rust
use lio_uring::SqeFlags;
// Write followed by fsync - fsync only runs if write succeeds
unsafe { sq.push_with_flags(&write_op, 1, SqeFlags::IO_LINK) }?;
unsafe { sq.push(&fsync_op, 2) }?;
sq.submit()?;
```
### Batch Submission
Submit multiple operations at once for better performance:
```rust
for (i, op) in operations.iter().enumerate() {
unsafe { sq.push(op, i as u64) }?;
}
let submitted = sq.submit()?; // Submit all at once
```
### Non-blocking Completions
Check for completions without blocking:
```rust
while let Some(completion) = cq.try_next()? {
println!("Got completion: {:?}", completion);
}
```
### Fixed Buffers and Files
Pre-register resources for zero-copy operations and improved performance.
#### Registered Buffers
Register buffers once, then reference them by index for zero-copy I/O:
```rust
use std::io::IoSlice;
// Prepare your buffers
let mut buffer1 = vec![0u8; 4096];
let mut buffer2 = vec![0u8; 4096];
// Register them with io_uring (pins them in kernel memory)
let buffers = vec![IoSlice::new(&buffer1), IoSlice::new(&buffer2)];
unsafe { sq.register_buffers(&buffers) }?;
// Use ReadFixed/WriteFixed with buf_index to reference registered buffers
let write_op = WriteFixed {
fd: file.as_raw_fd(),
buf: buffer1.as_ptr().cast(), // Still provide the pointer
nbytes: buffer1.len() as u32,
offset: 0,
buf_index: 0, // Reference first registered buffer
};
unsafe { sq.push(&write_op, 1) }?;
sq.submit()?;
// When done, unregister
sq.unregister_buffers()?;
```
**Benefits:**
- Zero-copy I/O (data doesn't cross user/kernel boundary)
- Buffers are pinned in physical memory (no page faults)
- Reduced CPU overhead
- Significant performance improvement for frequently used buffers
#### Registered Files
Register file descriptors once, then reference by index:
```rust
// Register files
sq.register_files(&[fd1, fd2, fd3])?;
// Use operations with SqeFlags::FIXED_FILE and fd index
let read_op = Read {
fd: 0, // Index in registered files array
ptr: buf.as_mut_ptr().cast(),
len: buf.len() as u32,
offset: 0,
};
unsafe { sq.push_with_flags(&read_op, 1, SqeFlags::FIXED_FILE) }?;
// Update specific indices
sq.register_files_update(1, &[new_fd])?; // Replace fd at index 1
// Unregister when done
sq.unregister_files()?;
```
**Benefits:**
- Faster fd lookup (no fd table traversal)
- Reduced per-operation overhead
### IO Polling
Enable busy-polling for lowest latency:
```rust
use lio_uring::IoUringParams;
let params = IoUringParams::default().iopoll();
let (mut cq, mut sq) = IoUring::with_params(params)?;
```
## Supported Operations
### File I/O
- `Read`, `Write`, `ReadFixed`, `WriteFixed`
- `Readv`, `Writev` (vectored I/O)
- `Openat`, `Openat2`, `Close`, `CloseFixed`
- `Fsync`, `Fadvise`, `Fallocate`, `Ftruncate`
- `Statx`
### Network I/O
- `Socket`, `SocketDirect`
- `Accept`, `AcceptMulti` (multishot)
- `Connect`, `Bind`, `Listen`, `Shutdown`
- `Send`, `Recv`, `SendZc`, `RecvMulti` (multishot)
- `Sendmsg`, `Recvmsg`, `SendmsgZc`
### File System
- `Unlinkat`, `Renameat`, `Mkdirat`
- `Linkat`, `Symlinkat`
- `Getxattr`, `Setxattr`, `Fgetxattr`, `Fsetxattr`
### Advanced
- `Nop` (no operation, useful for testing)
- `Timeout`, `TimeoutRemove`, `LinkTimeout`
- `PollAdd`, `PollRemove`, `PollUpdate`
- `Cancel` (cancel pending operation)
- `Splice`, `Tee` (zero-copy data transfer)
- `SyncFileRange`
- `MsgRing` (cross-ring communication)
- `Futex` operations (futex wait/wake)
- `ProvideBuffers`, `RemoveBuffers` (buffer rings)
## Examples
See the `examples/` directory for complete working examples:
- `basic_io.rs` - Basic file I/O operations (open, read, write, fsync, unlink)
- `linked_ops.rs` - Linked operations and batch submission
- `tcp_echo.rs` - TCP echo server using sockets
- `registered_buffers.rs` - Zero-copy I/O with registered buffers (ReadFixed/WriteFixed)
Run an example:
```bash
cargo run --example basic_io
cargo run --example registered_buffers
```
## Performance Tips
1. **Batch submissions**: Submit multiple operations at once with a single `submit()` call
2. **Use fixed buffers/files**: Pre-register frequently used resources
3. **Enable IOPOLL**: For lowest latency on fast storage
4. **Enable SQPOLL**: Offload submission to a kernel thread
5. **Link dependent operations**: Reduce round-trips for sequential operations
6. **Size the ring appropriately**: Balance memory usage vs. batching opportunities
## Testing
Run the test suite:
```bash
cargo test
```
Note: Tests require a Linux system with io_uring support (kernel 5.1+).
## Contributing
Contributions are welcome! Please ensure:
- All tests pass
- New features include tests
- Public APIs are documented
- Code follows Rust style guidelines
## License
MIT License - see LICENSE file for details
## References
- [io_uring documentation](https://kernel.dk/io_uring.pdf)
- [liburing](https://github.com/axboe/liburing)
- [Linux kernel io_uring interface](https://www.kernel.org/doc/html/latest/io_uring.html)