Created
September 7, 2022 12:56
-
-
Save nrc/18d5a57b3b37229b4c9c69038ce9eff2 to your computer and use it in GitHub Desktop.
async IO traits
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::io::Result; | |
use std::mem::{self, MaybeUninit}; | |
// A simple async-ification of sync Read plus downcasting methods. | |
pub trait Read { | |
// async | |
fn read(&mut self, buf: &mut [u8]) -> Result<usize>; | |
// ... | |
// TODO should we have dynamic and/or static (Option<&mut impl ReadyRead>) versions? | |
fn as_ready(&mut self) -> Option<&mut dyn ReadyRead> { | |
None | |
} | |
// TODO OwnedRead is not object-safe. Could either return impl OwnedRead, or maybe dyn* helps? | |
// Or change the sig of OwnedRead::read. | |
fn as_owned(&mut self) -> Option<&mut dyn OwnedRead> { | |
None | |
} | |
} | |
// Used for completion model systems. | |
pub trait OwnedRead: Read { | |
// async | |
fn read<B: OwnedBuf>(&mut self, buf: B) -> (B, Result<()>); | |
} | |
// Abstracts over Vec<u8> and similar data structures | |
pub trait OwnedBuf: Default + 'static { | |
fn take(&mut self) -> Self { | |
mem::take(self) | |
} | |
/// Returns a shared reference to the filled portion of the buffer. | |
fn filled(&self) -> &[u8]; | |
unsafe fn unfilled(&mut self) -> &mut [MaybeUninit<u8>]; | |
fn init_mut(&mut self) -> &mut [u8]; | |
fn uninit_mut(&mut self) -> &mut [MaybeUninit<u8>]; | |
unsafe fn advance(&mut self, n: usize); | |
unsafe fn set_init(&mut self, n: usize); | |
fn ensure_init(&mut self); | |
fn append(&mut self, buf: &[u8]); | |
// TODO does this work? | |
fn borrow<'a>(&'a mut self) -> BorrowedBuf<'a>; | |
} | |
// Used for epoll-like systems | |
pub trait Ready { | |
// async | |
fn ready(&mut self, interest: Interest) -> Result<Readiness>; | |
} | |
pub trait ReadyRead: Ready + Read { | |
fn non_blocking_read(&mut self, buf: &mut [u8]) -> Result<NonBlocking<usize>>; | |
// ... | |
} | |
/// Express which notifications the user is interested in receiving. | |
#[derive(Copy, Clone)] | |
pub struct Interest(u32); | |
/// Describes which operations are ready for an IO resource. | |
#[derive(Copy, Clone)] | |
pub struct Readiness(u32); | |
/// Whether an IO operation is ready for reading/writing or would block. | |
#[derive(Copy, Clone, Debug)] | |
pub enum NonBlocking<T> { | |
Ready(T), | |
WouldBlock, | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
// Some example readable resource, provided by a runtime. | |
struct Socket; | |
// A 'good citizen' implementation of Socket would support all appropriate traits (usually basic | |
// Read and one of the specialised traits). | |
impl Read for Socket {} | |
impl OwnedRead for Socket {} | |
impl ReadyRead for Socket {} | |
fn simple_read() -> Result<()> { | |
let mut s = Socket; | |
let mut buf = [0; 1024]; | |
s.read(&mut buf)?; | |
// Use buf | |
// See generic versions for owned/ready | |
} | |
fn generic_read(reader: &mut impl Read) -> Result<()> { | |
let mut buf = [0; 1024]; | |
reader.read(&mut buf)?; | |
// Use buf | |
} | |
fn generic_owned_read(reader: &mut impl OwnedRead) -> Result<()> { | |
let mut buf = vec![0; 1024]; | |
let result = reader.read(buf); | |
buf = result.0; | |
result.1?; | |
// Use buf | |
} | |
fn generic_ready_read(reader: &mut impl ReadyRead) -> Result<()> { | |
loop { | |
reader.ready(Interest::READABLE)/*.await*/?; | |
let mut buf = [0; 1024]; | |
if let NonBlocking::Ready(n) = reader.non_blocking_read(buf)? { | |
// Use buf | |
return Ok(()); | |
} | |
} | |
} | |
fn advanced_read(reader: &mut impl Read) -> Result<()> { | |
if let Some(reader) = reader.as_owned() { | |
let mut buf = vec![0; 1024]; | |
let result = reader.read(buf); | |
buf = result.0; | |
result.1?; | |
// Use buf | |
} else if let Some(reader) = reader.as_ready() { | |
loop { | |
reader.ready(Interest::READABLE)/*.await*/?; | |
let mut buf = [0; 1024]; | |
if let NonBlocking::Ready(n) = reader.non_blocking_read(buf)? { | |
// Use buf | |
return Ok(()); | |
} | |
} | |
} else { | |
let mut buf = [0; 1024]; | |
reader.read(&mut buf)?; | |
// Use buf | |
} | |
} | |
// An example of a 'middleware' library which should support all runtimes. | |
struct Buffer<T: Read> { | |
reader: T, | |
buf: Vec<u8>, | |
} | |
// Good citizen libraries have to support all three traits. | |
impl Read for Buffer { | |
// read should use an advanced_read style, since even if the caller does | |
// not use the trick, the advanced modes might be more efficient. | |
} | |
// These two impls have to downcast self.reader and either panic or use a | |
// slow path if the downcast fails. | |
impl OwnedRead for Buffer {} | |
impl ReadyRead for Buffer {} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment