Repository: withoutboats/ringbahn Branch: master Commit: 08e6464e64d8 Files: 55 Total size: 99.4 KB Directory structure: gitextract_7bu16q06/ ├── .gitignore ├── Cargo.toml ├── README.md ├── examples/ │ ├── copy-file.rs │ ├── echo-client.rs │ ├── echo-server.rs │ └── read-event.rs ├── props.txt ├── src/ │ ├── buf.rs │ ├── drive/ │ │ ├── demo.rs │ │ └── mod.rs │ ├── event/ │ │ ├── accept.rs │ │ ├── close.rs │ │ ├── connect.rs │ │ ├── epoll_ctl.rs │ │ ├── fadvise.rs │ │ ├── fallocate.rs │ │ ├── files_update.rs │ │ ├── fsync.rs │ │ ├── mod.rs │ │ ├── openat.rs │ │ ├── provide_buffers.rs │ │ ├── read.rs │ │ ├── readv.rs │ │ ├── recv.rs │ │ ├── send.rs │ │ ├── splice.rs │ │ ├── statx.rs │ │ ├── timeout.rs │ │ ├── write.rs │ │ └── writev.rs │ ├── fs.rs │ ├── io.rs │ ├── lib.rs │ ├── net/ │ │ ├── listener.rs │ │ ├── mod.rs │ │ └── stream.rs │ ├── ring/ │ │ ├── cancellation.rs │ │ ├── completion.rs │ │ └── mod.rs │ ├── submission.rs │ └── unix/ │ ├── listener.rs │ ├── mod.rs │ └── stream.rs └── tests/ ├── basic-read.rs ├── basic-readv.rs ├── basic-write.rs ├── basic-writev.rs ├── file-close.rs ├── file-read.rs ├── file-seek.rs ├── file-write.rs ├── println.rs ├── registered-fd.rs └── stdio.rs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ /target Cargo.lock ================================================ FILE: Cargo.toml ================================================ [package] name = "ringbahn" version = "0.0.0-experimental.3" authors = ["Without Boats "] description = "an experimental safe API for io-uring" repository = "https://github.com/withoutboats/ringbahn" license = "MIT OR Apache-2.0" edition = "2018" [dependencies] futures-io = "0.3.5" futures-core = "0.3.5" parking_lot = "0.10.2" once_cell = "1.3.1" libc = "0.2.71" uring-sys = "0.7.4" nix = "0.18.0" iou = "0.3.3" either = "1.6.1" event-listener = "2.5.1" [dev-dependencies] tempfile = "3.1.0" futures = { version = "0.3.5", features = ["thread-pool"] } ================================================ FILE: README.md ================================================ # ringbahn - a safe interface to io-uring The Berlin Ringbahn is a double-tracked commuter rail line which forms a complete ring around the center of the city of Berlin. Similarly, io-uring is a new interface for asynchronous IO with the Linux kernel built on a double ring buffer data structure. ringbahn is an attempt to define a good interface to perform IO on io-uring with these properties: - 100% memory safe - Completely non-blocking - Ergonomic with async/await syntax - A zero-cost abstraction with minimal overhead - Abstracted over different patterns for driving the io-uring instance - Misuse-resistant with a well implemented driver **The current version of ringbahn is highly experimental and insufficiently hardened for production use.** You are strongly recommended not to deploy the code under the current version. But tests, bug reports, user feedback, and other experiments are all welcome at this stage. Though ringbahn is a prototype, it demonstrates that a safe, ergonomic, efficient, and flexible interface to io-uring is possible in Rust. It should be a goal for the Rust community not only to have an adequate interface for io-uring, but to have the *best* interface for io-uring. ## License ringbahn is licensed under your choice of MIT or Apache-2.0. ================================================ FILE: examples/copy-file.rs ================================================ use futures::io::AsyncReadExt; use futures::io::AsyncWriteExt; use ringbahn::fs::File; fn main() { futures::executor::block_on(async move { let mut input: File = File::open("props.txt").await.unwrap(); let mut output: File = File::create("test.txt").await.unwrap(); let mut buf = vec![0; 1024]; let len = input.read(&mut buf).await.unwrap(); output.write(&mut buf[0..len]).await.unwrap(); output.flush().await.unwrap(); }); } ================================================ FILE: examples/echo-client.rs ================================================ use ringbahn::net::TcpStream; use std::io::{self, BufRead, Write}; use futures::io::{AsyncBufReadExt, AsyncWriteExt}; use futures::executor::block_on; fn main() { block_on(async move { let mut stream = TcpStream::connect(("127.0.0.1", 7878)).await.unwrap(); let stdin = io::stdin(); let stdout = io::stdout(); let mut stdout = stdout; let mut buf = String::new(); for line in stdin.lock().lines() { let line = line.unwrap(); stream.write_all(line.as_bytes()).await.unwrap(); stream.read_line(&mut buf).await.unwrap(); stdout.write_all(buf.as_bytes()).unwrap(); buf.clear(); } }) } ================================================ FILE: examples/echo-server.rs ================================================ use ringbahn::net::TcpListener; use futures::StreamExt; use futures::io::{AsyncReadExt, AsyncWriteExt}; use futures::executor::{ThreadPool, block_on}; fn main() { let mut listener = TcpListener::bind(("127.0.0.1", 7878)).unwrap(); println!("listening on port 7878"); let mut incoming = listener.incoming(); let pool = ThreadPool::new().unwrap(); block_on(async move { while let Some(stream) = incoming.next().await { println!("recieved connection"); let (mut stream, _) = stream.unwrap(); pool.spawn_ok(async move { loop { let mut buf = [0; 8096]; let n = stream.read(&mut buf[..]).await.unwrap(); println!("read {} bytes", n); buf[n] = b'\n'; stream.write_all(&buf[0..n + 1]).await.unwrap(); println!("write {} bytes", n + 1); } }); } }); } ================================================ FILE: examples/read-event.rs ================================================ use ringbahn::*; use std::fs::{metadata, File}; use std::io; use std::os::unix::io::AsRawFd; fn main() -> io::Result<()> { let driver = drive::demo::driver(); let meta = metadata("props.txt")?; let file = File::open("props.txt")?; let event = event::Read { fd: file.as_raw_fd(), buf: vec![0; meta.len() as usize].into(), offset: 0 }; let submission = Submission::new(event, driver.clone()); futures::executor::block_on(async move { let (event, result) = submission.await; let bytes_read = result? as usize; let content = String::from_utf8_lossy(&event.buf[0..bytes_read]).to_string(); ringbahn::print!(driver, "{}", content).await; Ok(()) }) } ================================================ FILE: props.txt ================================================ But this formidable power of death - and this is perhaps what accounts for part of its force and the cynicism with which it has so greatly expanded its limits - now presents itself as the counterpart of a power that exerts a positive influence on life, that endeavors to administer, optimize, and multiply it, subjecting it to precise controls and comprehensive regulations. Wars are no longer waged in the name of a sovereign who must be defended; they are waged on behalf of the existence of everyone; entire populations are mobilized for the purpose of wholesale slaughter in the name of life necessity: massacres have become vital. It is as managers of life and survival, of bodies and the race, that so many regimes have been able to wage so many wars, causing so many men to be killed. ================================================ FILE: src/buf.rs ================================================ use std::cmp; use std::io; use std::task::Poll; use futures_core::ready; use crate::ring::Cancellation; #[derive(Default, Debug)] pub struct Buffer { data: Option>, pos: u32, cap: u32, } impl Buffer { pub fn buffered_from_read(&self) -> &[u8] { self.data.as_deref().map_or(&[], |data| &data[self.pos as usize..self.cap as usize]) } pub fn fill_buf(&mut self, fill: impl FnOnce(&mut [u8]) -> Poll>) -> Poll> { const CAPACITY: usize = 4096 * 2; if self.pos >= self.cap { if self.data.is_none() { self.data = Some(vec![0; CAPACITY].into_boxed_slice()); } self.cap = ready!(fill(self.data.as_deref_mut().unwrap()))?; self.pos = 0; } Poll::Ready(Ok(self.buffered_from_read())) } pub fn consume(&mut self, amt: usize) { self.pos = cmp::min(self.pos + amt as u32, self.cap); } pub fn clear(&mut self) { self.pos = 0; self.cap = 0; } pub fn into_boxed_slice(self) -> Option> { self.data } pub fn cancellation(&mut self) -> Cancellation { Cancellation::from(self.data.take()) } } ================================================ FILE: src/drive/demo.rs ================================================ //! A demo driver for experimentation purposes use std::future::Future; use std::io; use std::pin::Pin; use std::sync::Once; use std::task::{Poll, Context}; use std::thread; use event_listener::*; use futures_core::ready; use once_cell::sync::Lazy; use parking_lot::Mutex; const ENTRIES: u32 = 32; use super::{Drive, Completion}; use iou::*; type Queues = ( Mutex>, Mutex>, Registrar<'static>, Event, ); static QUEUES: Lazy = Lazy::new(init); /// The driver handle pub struct DemoDriver { listener: Option, } impl DemoDriver { fn poll_submit_inner(&mut self, ctx: &mut Context<'_>, sq: &mut SubmissionQueue<'_>) -> Poll> { start_completion_thread(); if let Some(listener) = &mut self.listener { ready!(Pin::new(listener).poll(ctx)); } match sq.submit() { Ok(n) => Poll::Ready(Ok(n)), Err(err) => { if err.raw_os_error().map_or(false, |code| code == libc::EBUSY) { self.listener = Some(QUEUES.3.listen()); Poll::Pending } else { Poll::Ready(Err(err)) } } } } } impl Default for DemoDriver { fn default() -> Self { driver() } } impl Clone for DemoDriver { fn clone(&self) -> DemoDriver { driver() } } impl Drive for DemoDriver { fn poll_prepare<'cx>( mut self: Pin<&mut Self>, ctx: &mut Context<'cx>, count: u32, prepare: impl FnOnce(SQEs<'_>, &mut Context<'cx>) -> Completion<'cx>, ) -> Poll> { let mut sq = QUEUES.0.lock(); loop { match sq.prepare_sqes(count) { Some(sqs) => return Poll::Ready(prepare(sqs, ctx)), None => { let _ = ready!(self.poll_submit_inner(ctx, &mut *sq)); } } } } fn poll_submit( mut self: Pin<&mut Self>, ctx: &mut Context<'_>, ) -> Poll> { self.poll_submit_inner(ctx, &mut *QUEUES.0.lock()) } } /// Construct a demo driver handle pub fn driver() -> DemoDriver { DemoDriver { listener: None, } } /// Access the registrar /// /// This will return `None` if events have already been submitted to the driver. The Demo Driver /// currently only allows registering IO objects prior to submitting IO. pub fn registrar() -> Option<&'static Registrar<'static>> { if !STARTED_COMPLETION_THREAD.is_completed() { Some(&QUEUES.2) } else { None } } fn init() -> Queues { let flags = SetupFlags::empty(); let features = SetupFeatures::NODROP; let ring = Box::new(IoUring::new_with_flags(ENTRIES, flags, features).unwrap()); let ring = Box::leak(ring); let (sq, cq, reg) = ring.queues(); (Mutex::new(sq), Mutex::new(cq), reg, Event::new()) } static STARTED_COMPLETION_THREAD: Once = Once::new(); fn start_completion_thread() { STARTED_COMPLETION_THREAD.call_once(|| { thread::spawn(move || { let mut cq = QUEUES.1.lock(); while let Ok(cqe) = cq.wait_for_cqe() { let mut ready = cq.ready() as usize + 1; QUEUES.3.notify_additional(ready); super::complete(cqe); ready -= 1; while let Some(cqe) = cq.peek_for_cqe() { if ready == 0 { ready = cq.ready() as usize + 1; QUEUES.3.notify_additional(ready); } super::complete(cqe); ready -= 1; } debug_assert!(ready == 0); } }); }); } ================================================ FILE: src/drive/mod.rs ================================================ //! Drive IO on io-uring pub mod demo; use std::io; use std::marker::PhantomData; use std::pin::Pin; use std::task::{Context, Poll}; use crate::ring; use crate::{Submission, Event}; use iou::{SQE, SQEs}; pub use crate::ring::completion::complete; /// A completion which will be used to wake the task waiting on this event. /// /// This type is opaque to users of ringbahn. It is constructed by the callback passed to /// [Drive::poll_prepare]. pub struct Completion<'cx> { pub(crate) real: ring::Completion, marker: PhantomData &'cx ()>, } impl<'cx> Completion<'cx> { pub(crate) fn new(mut sqe: SQE<'_>, _sqes: SQEs<'_>, cx: &mut Context<'cx>) -> Completion<'cx> { let real = ring::Completion::new(cx.waker().clone()); unsafe { sqe.set_user_data(real.addr()); } Completion { real, marker: PhantomData } } } /// Implemented by drivers for io-uring. /// /// The type that implements `Drive` is used to prepare and submit IO events to an io-uring /// instance. Paired with a piece of code which processes completions, it can run IO on top of /// io-uring. pub trait Drive { /// Prepare an event on the submission queue. /// /// The implementer is responsible for provisioning an [`iou::SQE`] from the /// submission queue. Once an SQE is available, the implementer should pass it to the /// `prepare` callback, which constructs a [`Completion`], and return that `Completion` to the /// caller. /// /// If the driver is not ready to receive more events, it can return `Poll::Pending`. If it /// does, it must register a waker to wake the task when more events can be prepared, otherwise /// this method will not be called again. This allows the driver to implement backpressure. /// /// Drivers which call `prepare` but do not return the completion it gives are incorrectly /// implemented. This will lead ringbahn to panic. fn poll_prepare<'cx>( self: Pin<&mut Self>, ctx: &mut Context<'cx>, count: u32, prepare: impl FnOnce(SQEs<'_>, &mut Context<'cx>) -> Completion<'cx>, ) -> Poll>; /// Suggest to submit all of the events on the submission queue. /// /// The implementer is responsible for determining how and when events are submitted to the /// kernel to complete. It is valid for this function to do nothing at all; this function just /// informs the driver that the user program is waiting for its prepared events to be /// submitted and completed. /// /// If the implementation is not ready to submit, but wants to be called again to try later, it /// can return `Poll::Pending`. If it does, it must register a waker to wake the task when it /// would be appropriate to try submitting again. /// /// It is also valid not to submit an event but not to register a waker to try again, in which /// case the appropriate response would be to return `Ok(0)`. This indicates to the caller that /// the submission step is complete, whether or not actual IO was performed by the driver. fn poll_submit( self: Pin<&mut Self>, ctx: &mut Context<'_>, ) -> Poll>; fn submit(self, event: E) -> Submission where Self: Sized { Submission::new(event, self) } } ================================================ FILE: src/event/accept.rs ================================================ use std::mem::ManuallyDrop; use std::os::unix::io::RawFd; use iou::sqe::{SockFlag, SockAddrStorage}; use iou::registrar::UringFd; use super::{Event, SQE, SQEs, Cancellation}; pub struct Accept { pub addr: Option>, pub fd: FD, pub flags: SockFlag, } impl Event for Accept { fn sqes_needed(&self) -> u32 { 1 } unsafe fn prepare<'sq>(&mut self, sqs: &mut SQEs<'sq>) -> SQE<'sq> { let mut sqe = sqs.single().unwrap(); sqe.prep_accept(self.fd, self.addr.as_deref_mut(), self.flags); sqe } fn cancel(this: ManuallyDrop) -> Cancellation { Cancellation::from(ManuallyDrop::into_inner(this).addr) } } ================================================ FILE: src/event/close.rs ================================================ use std::os::unix::io::RawFd; use iou::registrar::UringFd; use super::{Event, SQE, SQEs}; pub struct Close { pub fd: FD, } impl Event for Close { fn sqes_needed(&self) -> u32 { 1 } unsafe fn prepare<'sq>(&mut self, sqs: &mut SQEs<'sq>) -> SQE<'sq> { let mut sqe = sqs.single().unwrap(); sqe.prep_close(self.fd); sqe } } ================================================ FILE: src/event/connect.rs ================================================ use std::mem::ManuallyDrop; use std::os::unix::io::RawFd; use iou::sqe::SockAddr; use iou::registrar::UringFd; use super::{Event, SQE, SQEs, Cancellation}; pub struct Connect { pub fd: FD, pub addr: Box, } impl Event for Connect { fn sqes_needed(&self) -> u32 { 1 } unsafe fn prepare<'sq>(&mut self, sqs: &mut SQEs<'sq>) -> SQE<'sq> { let mut sqe = sqs.single().unwrap(); sqe.prep_connect(self.fd, &mut *self.addr); sqe } fn cancel(this: ManuallyDrop) -> Cancellation { Cancellation::from(ManuallyDrop::into_inner(this).addr) } } ================================================ FILE: src/event/epoll_ctl.rs ================================================ use std::mem::ManuallyDrop; use std::os::unix::io::RawFd; use iou::sqe::{EpollOp, EpollEvent}; use super::{Event, SQE, SQEs, Cancellation}; pub struct EpollCtl { pub epoll_fd: RawFd, pub op: EpollOp, pub fd: RawFd, pub event: Option>, } impl Event for EpollCtl { fn sqes_needed(&self) -> u32 { 1 } unsafe fn prepare<'sq>(&mut self, sqs: &mut SQEs<'sq>) -> SQE<'sq> { let mut sqe = sqs.single().unwrap(); sqe.prep_epoll_ctl(self.epoll_fd, self.op, self.fd, self.event.as_deref_mut()); sqe } fn cancel(this: ManuallyDrop) -> Cancellation { Cancellation::from(ManuallyDrop::into_inner(this).event) } } ================================================ FILE: src/event/fadvise.rs ================================================ use std::os::unix::io::RawFd; use iou::sqe::PosixFadviseAdvice; use iou::registrar::UringFd; use super::{Event, SQE, SQEs}; pub struct Fadvise { pub fd: FD, pub offset: u64, pub size: u64, pub flags: PosixFadviseAdvice, } impl Event for Fadvise { fn sqes_needed(&self) -> u32 { 1 } unsafe fn prepare<'sq>(&mut self, sqs: &mut SQEs<'sq>) -> SQE<'sq> { let mut sqe = sqs.single().unwrap(); sqe.prep_fadvise(self.fd, self.offset, self.size, self.flags); sqe } } ================================================ FILE: src/event/fallocate.rs ================================================ use std::os::unix::io::RawFd; use iou::registrar::UringFd; use iou::sqe::FallocateFlags; use super::{Event, SQE, SQEs}; pub struct Fallocate { pub fd: FD, pub offset: u64, pub size: u64, pub flags: FallocateFlags, } impl Event for Fallocate { fn sqes_needed(&self) -> u32 { 1 } unsafe fn prepare<'sq>(&mut self, sqs: &mut SQEs<'sq>) -> SQE<'sq> { let mut sqe = sqs.single().unwrap(); sqe.prep_fallocate(self.fd, self.offset, self.size, self.flags); sqe } } ================================================ FILE: src/event/files_update.rs ================================================ use std::mem::ManuallyDrop; use std::os::unix::io::RawFd; use super::{Event, SQE, SQEs, Cancellation}; pub struct FilesUpdate { pub files: Box<[RawFd]>, pub offset: u32, } impl Event for FilesUpdate { fn sqes_needed(&self) -> u32 { 1 } unsafe fn prepare<'sq>(&mut self, sqs: &mut SQEs<'sq>) -> SQE<'sq> { let mut sqe = sqs.single().unwrap(); sqe.prep_files_update(&self.files[..], self.offset); sqe } fn cancel(this: ManuallyDrop) -> Cancellation { Cancellation::from(ManuallyDrop::into_inner(this).files) } } ================================================ FILE: src/event/fsync.rs ================================================ use std::os::unix::io::RawFd; use iou::registrar::UringFd; use iou::sqe::FsyncFlags; use super::{Event, SQE, SQEs}; pub struct Fsync { pub fd: FD, pub flags: FsyncFlags, } impl Event for Fsync { fn sqes_needed(&self) -> u32 { 1 } unsafe fn prepare<'sq>(&mut self, sqs: &mut SQEs<'sq>) -> SQE<'sq> { let mut sqe = sqs.single().unwrap(); sqe.prep_fsync(self.fd, self.flags); sqe } } ================================================ FILE: src/event/mod.rs ================================================ //! Events that can be scheduled on io-uring with a [`Submission`](crate::Submission) mod accept; mod close; mod connect; mod epoll_ctl; mod fadvise; mod fallocate; mod files_update; mod fsync; mod openat; mod provide_buffers; mod read; mod readv; mod recv; mod send; mod splice; mod statx; mod timeout; mod write; mod writev; use std::mem::ManuallyDrop; use iou::{SQE, SQEs}; use crate::ring::Cancellation; pub use accept::Accept; pub use close::Close; pub use connect::Connect; pub use epoll_ctl::EpollCtl; pub use fadvise::Fadvise; pub use fallocate::Fallocate; pub use files_update::FilesUpdate; pub use fsync::Fsync; pub use openat::OpenAt; pub use provide_buffers::{ProvideBuffers, RemoveBuffers}; pub use read::{Read, ReadFixed}; pub use readv::ReadVectored; pub use recv::Recv; pub use send::Send; pub use splice::Splice; pub use statx::Statx; pub use timeout::{Timeout, StaticTimeout}; pub use write::{Write, WriteFixed}; pub use writev::WriteVectored; /// An IO event that can be scheduled on an io-uring driver. /// /// ## Safety /// /// Event is a safe trait with two unsafe methods. It's important to understand that when /// implementing an unsafe method, the code author implementing that method is allowed to assume /// certain additional invariants will be upheld by all callers. It is the caller's responsibility /// to ensure those invariants are upheld, not the implementer. However, any unsafe operations /// performed inside of the method must be safe under those invariants and any other invariants the /// implementer has upheld. The implementer is not allowed to add any additional invariants that /// the caller must uphold that are not required by the trait. pub trait Event { fn sqes_needed(&self) -> u32; /// Prepare an event to be submitted using the SQE argument. /// /// ## Safety /// /// When this method is called, these guarantees will be maintained by the caller: /// /// The data contained by this event will not be accessed again by this program until one of /// two things happen: /// - The event being prepared has been completed by the kernel, in which case ownership of /// this event will be passed back to users of this library. /// - Interest in the event is cancelled, in which case `Event::cancel` will be called and the /// event's destructor will not run. /// /// In essence implementing prepare, users can write code ass if any heap addresses passed to /// the kernel have passed ownership of that data to the kernel for the time that the event is /// completed. unsafe fn prepare<'a>(&mut self, sqs: &mut SQEs<'a>) -> SQE<'a>; /// Return the cancellation callback for this event. /// /// If this event is cancelled, this callback will be stored with the completion to be dropped /// when the IO event completes. This way, any managed resources passed to the kernel (like /// buffers) can be cleaned up once the kernel no longer needs them. fn cancel(_: ManuallyDrop) -> Cancellation where Self: Sized { Cancellation::from(()) } } ================================================ FILE: src/event/openat.rs ================================================ use std::ffi::CString; use std::mem::ManuallyDrop; use std::os::unix::io::RawFd; use std::os::unix::ffi::OsStrExt; use std::path::Path; use iou::sqe::{Mode, OFlag}; use super::{Event, SQE, SQEs, Cancellation}; pub struct OpenAt { pub path: CString, pub dir_fd: RawFd, pub flags: OFlag, pub mode: Mode, } impl OpenAt { pub fn without_dir(path: impl AsRef, flags: OFlag, mode: Mode) -> OpenAt { let path = CString::new(path.as_ref().as_os_str().as_bytes()).unwrap(); OpenAt { path, dir_fd: libc::AT_FDCWD, flags, mode } } } impl Event for OpenAt { fn sqes_needed(&self) -> u32 { 1 } unsafe fn prepare<'sq>(&mut self, sqs: &mut SQEs<'sq>) -> SQE<'sq> { let mut sqe = sqs.single().unwrap(); sqe.prep_openat(self.dir_fd, &*self.path, self.flags, self.mode); sqe } fn cancel(this: ManuallyDrop) -> Cancellation { Cancellation::from(ManuallyDrop::into_inner(this).path) } } ================================================ FILE: src/event/provide_buffers.rs ================================================ use std::mem::ManuallyDrop; use iou::sqe::BufferGroupId; use super::{Event, SQE, SQEs, Cancellation}; pub struct ProvideBuffers { pub bufs: Box<[u8]>, pub count: u32, pub group: BufferGroupId, pub index: u32, } impl Event for ProvideBuffers { fn sqes_needed(&self) -> u32 { 1 } unsafe fn prepare<'sq>(&mut self, sqs: &mut SQEs<'sq>) -> SQE<'sq> { let mut sqe = sqs.single().unwrap(); sqe.prep_provide_buffers(&mut self.bufs[..], self.count, self.group, self.index); sqe } fn cancel(this: ManuallyDrop) -> Cancellation { Cancellation::from(ManuallyDrop::into_inner(this).bufs) } } pub struct RemoveBuffers { pub count: u32, pub group: BufferGroupId, } impl Event for RemoveBuffers { fn sqes_needed(&self) -> u32 { 1 } unsafe fn prepare<'sq>(&mut self, sqs: &mut SQEs<'sq>) -> SQE<'sq> { let mut sqe = sqs.single().unwrap(); sqe.prep_remove_buffers(self.count, self.group); sqe } } ================================================ FILE: src/event/read.rs ================================================ use std::mem::ManuallyDrop; use std::os::unix::io::RawFd; use iou::registrar::{UringFd, RegisteredBuf}; use super::{Event, SQE, SQEs, Cancellation}; /// A basic read event. pub struct Read { pub fd: FD, pub buf: Box<[u8]>, pub offset: u64, } impl Event for Read { fn sqes_needed(&self) -> u32 { 1 } unsafe fn prepare<'sq>(&mut self, sqs: &mut SQEs<'sq>) -> SQE<'sq> { let mut sqe = sqs.single().unwrap(); sqe.prep_read(self.fd, &mut self.buf[..], self.offset); sqe } fn cancel(this: ManuallyDrop) -> Cancellation { Cancellation::from(ManuallyDrop::into_inner(this).buf) } } pub struct ReadFixed { pub fd: FD, pub buf: RegisteredBuf, pub offset: u64, } impl Event for ReadFixed { fn sqes_needed(&self) -> u32 { 1 } unsafe fn prepare<'sq>(&mut self, sqs: &mut SQEs<'sq>) -> SQE<'sq> { let mut sqe = sqs.single().unwrap(); sqe.prep_read(self.fd, self.buf.as_mut(), self.offset); sqe } fn cancel(this: ManuallyDrop) -> Cancellation { Cancellation::from(ManuallyDrop::into_inner(this).buf) } } ================================================ FILE: src/event/readv.rs ================================================ use std::io::IoSliceMut; use std::mem::ManuallyDrop; use std::os::unix::io::RawFd; use iou::registrar::UringFd; use super::{Event, SQE, SQEs, Cancellation}; /// A `readv` event. pub struct ReadVectored { pub fd: FD, pub bufs: Box<[Box<[u8]>]>, pub offset: u64, } impl ReadVectored { fn as_iovecs(buffers: &mut [Box<[u8]>]) -> &mut [IoSliceMut] { // Unsafe contract: // This pointer cast is defined behaviour because Box<[u8]> (wide pointer) // is currently ABI compatible with libc::iovec. // // Then, libc::iovec is guaranteed ABI compatible with IoSliceMut on Unix: // https://doc.rust-lang.org/beta/std/io/struct.IoSliceMut.html // // We are relying on the internals of Box<[u8]>, but this is such a // foundational part of Rust it's unlikely the data layout would change // without warning. // // Pointer cast expression adapted from the "Turning a &mut T into an &mut U" // example of: https://doc.rust-lang.org/std/mem/fn.transmute.html#alternatives unsafe { &mut *(buffers as *mut [Box<[u8]>] as *mut [IoSliceMut]) } } } impl Event for ReadVectored { fn sqes_needed(&self) -> u32 { 1 } unsafe fn prepare<'sq>(&mut self, sqs: &mut SQEs<'sq>) -> SQE<'sq> { let mut sqe = sqs.single().unwrap(); sqe.prep_read_vectored(self.fd, Self::as_iovecs(&mut self.bufs[..]), self.offset); sqe } fn cancel(this: ManuallyDrop) -> Cancellation { Cancellation::from(ManuallyDrop::into_inner(this).bufs) } } ================================================ FILE: src/event/recv.rs ================================================ use std::mem::ManuallyDrop; use std::os::unix::io::RawFd; use iou::sqe::MsgFlags; use iou::registrar::UringFd; use super::{Event, SQE, SQEs, Cancellation}; pub struct Recv { pub fd: FD, pub buf: Box<[u8]>, pub flags: MsgFlags, } impl Event for Recv { fn sqes_needed(&self) -> u32 { 1 } unsafe fn prepare<'sq>(&mut self, sqs: &mut SQEs<'sq>) -> SQE<'sq> { let mut sqe = sqs.single().unwrap(); sqe.prep_recv(self.fd, &mut self.buf[..], self.flags); sqe } fn cancel(this: ManuallyDrop) -> Cancellation { Cancellation::from(ManuallyDrop::into_inner(this).buf) } } ================================================ FILE: src/event/send.rs ================================================ use std::mem::ManuallyDrop; use std::os::unix::io::RawFd; use iou::sqe::MsgFlags; use iou::registrar::UringFd; use super::{Event, SQE, SQEs, Cancellation}; pub struct Send { pub fd: FD, pub buf: Box<[u8]>, pub flags: MsgFlags, } impl Event for Send { fn sqes_needed(&self) -> u32 { 1 } unsafe fn prepare<'sq>(&mut self, sqs: &mut SQEs<'sq>) -> SQE<'sq> { let mut sqe = sqs.single().unwrap(); sqe.prep_send(self.fd, &self.buf[..], self.flags); sqe } fn cancel(this: ManuallyDrop) -> Cancellation { Cancellation::from(ManuallyDrop::into_inner(this).buf) } } ================================================ FILE: src/event/splice.rs ================================================ use std::os::unix::io::RawFd; use iou::sqe::SpliceFlags; use super::{Event, SQE, SQEs}; pub struct Splice { pub fd_in: RawFd, pub off_in: i64, pub fd_out: RawFd, pub off_out: i64, pub bytes: u32, pub flags: SpliceFlags, } impl Event for Splice { fn sqes_needed(&self) -> u32 { 1 } unsafe fn prepare<'sq>(&mut self, sqs: &mut SQEs<'sq>) -> SQE<'sq> { let mut sqe = sqs.single().unwrap(); sqe.prep_splice(self.fd_in, self.off_in, self.fd_out, self.off_out, self.bytes, self.flags); sqe } } ================================================ FILE: src/event/statx.rs ================================================ use std::ffi::CString; use std::mem::{self, ManuallyDrop}; use std::os::unix::io::RawFd; use std::os::unix::ffi::OsStrExt; use std::path::Path; use iou::sqe::{StatxFlags, StatxMode}; use iou::registrar::UringFd; use super::{Event, SQE, SQEs, Cancellation}; pub struct Statx { pub dir_fd: FD, pub path: CString, pub flags: StatxFlags, pub mask: StatxMode, pub statx: Box, } impl Statx { pub fn without_dir(path: impl AsRef, flags: StatxFlags, mask: StatxMode) -> Statx { let path = CString::new(path.as_ref().as_os_str().as_bytes()).unwrap(); let statx = unsafe { Box::new(mem::zeroed()) }; Statx { path, dir_fd: libc::AT_FDCWD, flags, mask, statx } } } impl Statx { pub fn without_path(fd: FD, mut flags: StatxFlags, mask: StatxMode) -> Statx { unsafe { // TODO don't allocate? Use Cow? Use NULL? let path = CString::new("").unwrap(); let statx = Box::new(mem::zeroed()); flags.insert(StatxFlags::AT_EMPTY_PATH); Statx { dir_fd: fd, path, flags, mask, statx } } } } impl Event for Statx { fn sqes_needed(&self) -> u32 { 1 } unsafe fn prepare<'sq>(&mut self, sqs: &mut SQEs<'sq>) -> SQE<'sq> { let mut sqe = sqs.single().unwrap(); sqe.prep_statx(self.dir_fd, self.path.as_c_str(), self.flags, self.mask, &mut *self.statx); sqe } fn cancel(this: ManuallyDrop) -> Cancellation { let this = ManuallyDrop::into_inner(this); Cancellation::from((this.statx, this.path)) } } ================================================ FILE: src/event/timeout.rs ================================================ use std::mem::ManuallyDrop; use std::time::Duration; use super::{Event, SQE, SQEs, Cancellation}; use iou::sqe::TimeoutFlags; pub struct StaticTimeout { ts: uring_sys::__kernel_timespec, events: u32, flags: TimeoutFlags, } impl StaticTimeout { pub const fn new(duration: Duration, events: u32, flags: TimeoutFlags) -> StaticTimeout { StaticTimeout { ts: timespec(duration), events, flags, } } } impl Event for &'static StaticTimeout { fn sqes_needed(&self) -> u32 { 1 } unsafe fn prepare<'sq>(&mut self, sqs: &mut SQEs<'sq>) -> SQE<'sq> { let mut sqe = sqs.single().unwrap(); sqe.prep_timeout(&self.ts, self.events, self.flags); sqe } } pub struct Timeout { ts: Box, events: u32, flags: TimeoutFlags, } impl Timeout { pub fn new(duration: Duration, events: u32, flags: TimeoutFlags) -> Timeout { Timeout { ts: Box::new(timespec(duration)), events, flags, } } } impl Event for Timeout { fn sqes_needed(&self) -> u32 { 1 } unsafe fn prepare<'sq>(&mut self, sqs: &mut SQEs<'sq>) -> SQE<'sq> { let mut sqe = sqs.single().unwrap(); sqe.prep_timeout(&*self.ts, self.events, self.flags); sqe } fn cancel(this: ManuallyDrop) -> Cancellation { Cancellation::from(ManuallyDrop::into_inner(this).ts) } } const fn timespec(duration: Duration) -> uring_sys::__kernel_timespec { uring_sys::__kernel_timespec { tv_sec: duration.as_secs() as i64, tv_nsec: duration.subsec_nanos() as _, } } ================================================ FILE: src/event/write.rs ================================================ use std::mem::ManuallyDrop; use std::os::unix::io::RawFd; use iou::registrar::{UringFd, RegisteredBuf}; use super::{Event, SQE, SQEs, Cancellation}; /// A basic write event. pub struct Write { pub fd: FD, pub buf: Box<[u8]>, pub offset: u64, } impl Event for Write { fn sqes_needed(&self) -> u32 { 1 } unsafe fn prepare<'sq>(&mut self, sqs: &mut SQEs<'sq>) -> SQE<'sq> { let mut sqe = sqs.single().unwrap(); sqe.prep_write(self.fd, &self.buf[..], self.offset); sqe } fn cancel(this: ManuallyDrop) -> Cancellation { Cancellation::from(ManuallyDrop::into_inner(this).buf) } } pub struct WriteFixed { pub fd: FD, pub buf: RegisteredBuf, pub offset: u64, } impl Event for WriteFixed { fn sqes_needed(&self) -> u32 { 1 } unsafe fn prepare<'sq>(&mut self, sqs: &mut SQEs<'sq>) -> SQE<'sq> { let mut sqe = sqs.single().unwrap(); sqe.prep_write(self.fd, self.buf.as_ref(), self.offset); sqe } fn cancel(this: ManuallyDrop) -> Cancellation { Cancellation::from(ManuallyDrop::into_inner(this).buf) } } ================================================ FILE: src/event/writev.rs ================================================ use std::io::IoSlice; use std::mem::ManuallyDrop; use std::os::unix::io::RawFd; use iou::registrar::UringFd; use super::{Event, SQE, SQEs, Cancellation}; /// A `writev` event. pub struct WriteVectored { pub fd: FD, pub bufs: Box<[Box<[u8]>]>, pub offset: u64, } impl WriteVectored { fn iovecs(&self) -> &[IoSlice] { unsafe { & *(&self.bufs[..] as *const [Box<[u8]>] as *const [IoSlice]) } } } impl Event for WriteVectored { fn sqes_needed(&self) -> u32 { 1 } unsafe fn prepare<'sq>(&mut self, sqs: &mut SQEs<'sq>) -> SQE<'sq> { let mut sqe = sqs.single().unwrap(); sqe.prep_write_vectored(self.fd, self.iovecs(), self.offset); sqe } fn cancel(this: ManuallyDrop) -> Cancellation { Cancellation::from(ManuallyDrop::into_inner(this).bufs) } } ================================================ FILE: src/fs.rs ================================================ //! Interact with the file system using io-uring use std::fs; use std::future::Future; use std::io; use std::mem::{self, ManuallyDrop}; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; use std::path::Path; use std::pin::Pin; use std::task::{Context, Poll}; use either::Either; use futures_core::ready; use futures_io::{AsyncRead, AsyncBufRead, AsyncWrite, AsyncSeek}; use iou::sqe::{OFlag, Mode}; use crate::buf::Buffer; use crate::drive::Drive; use crate::drive::demo::DemoDriver; use crate::ring::{Ring, Cancellation}; use crate::event::OpenAt; use crate::Submission; type FileBuf = Either>; /// A file handle that runs on io-uring pub struct File { ring: Ring, fd: RawFd, active: Op, buf: FileBuf, pos: u64, } #[derive(Copy, Clone, Debug, Eq, PartialEq)] enum Op { Read, Write, Close, Nothing, Statx, Closed, } impl File { /// Open a file using the default driver pub fn open(path: impl AsRef) -> Open { File::open_on_driver(path, DemoDriver::default()) } /// Create a new file using the default driver pub fn create(path: impl AsRef) -> Create { File::create_on_driver(path, DemoDriver::default()) } } impl File { /// Open a file pub fn open_on_driver(path: impl AsRef, driver: D) -> Open { let flags = OFlag::O_CLOEXEC | OFlag::O_RDONLY; Open(driver.submit(OpenAt::without_dir(path, flags, Mode::from_bits(0o666).unwrap()))) } /// Create a file pub fn create_on_driver(path: impl AsRef, driver: D) -> Create { let flags = OFlag::O_CLOEXEC | OFlag::O_WRONLY | OFlag::O_CREAT | OFlag::O_TRUNC; Create(driver.submit(OpenAt::without_dir(path, flags, Mode::from_bits(0o666).unwrap()))) } } impl File { /// Take an existing file and run its IO on an io-uring driver pub fn run_on_driver(file: fs::File, driver: D) -> File { let file = ManuallyDrop::new(file); File::from_fd(file.as_raw_fd(), driver) } fn from_fd(fd: RawFd, driver: D) -> File { File { ring: Ring::new(driver), active: Op::Nothing, buf: Either::Left(Buffer::default()), pos: 0, fd, } } /// Access any data that has been read into the buffer, but not consumed /// /// This is similar to the fill_buf method from AsyncBufRead, but instead of performing IO if /// the buffer is empty, it will just return an empty slice. This method can be used to copy /// out any left over buffered data before closing or performing a write. pub fn read_buffered(&self) -> &[u8] { if self.active == Op::Read { self.buf.as_ref().unwrap_left().buffered_from_read() } else { &[] } } fn guard_op(self: Pin<&mut Self>, op: Op) { let (ring, buf, .., active) = self.split(); if *active == Op::Closed { panic!("Attempted to perform IO on a closed File"); } else if *active != Op::Nothing && *active != op { let new_buf = Either::Left(Buffer::default()); ring.cancel_pinned(Cancellation::from(mem::replace(buf, new_buf))); } *active = op; } fn cancel(&mut self) { self.active = Op::Nothing; let new_buf = Either::Left(Buffer::default()); self.ring.cancel(Cancellation::from(mem::replace(&mut self.buf, new_buf))); } fn poll_file_size(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { static EMPTY: libc::c_char = 0; use std::ffi::CStr; self.as_mut().guard_op(Op::Statx); let fd = self.fd; let (ring, statx, ..) = self.split_with_statx(); let flags = iou::sqe::StatxFlags::AT_EMPTY_PATH; let mask = iou::sqe::StatxMode::STATX_SIZE; ready!(ring.poll(ctx, 1, |sqs| { let mut sqe = sqs.single().unwrap(); unsafe { sqe.prep_statx(fd, CStr::from_ptr(&EMPTY), flags, mask, statx); } sqe }))?; Poll::Ready(Ok((*statx).stx_size)) } #[inline(always)] fn split(self: Pin<&mut Self>) -> (Pin<&mut Ring>, &mut FileBuf, &mut u64, &mut Op) { unsafe { let this = Pin::get_unchecked_mut(self); (Pin::new_unchecked(&mut this.ring), &mut this.buf, &mut this.pos, &mut this.active) } } #[inline(always)] fn split_with_buf(self: Pin<&mut Self>) -> (Pin<&mut Ring>, &mut Buffer, &mut u64, &mut Op) { let (ring, buf, pos, active) = self.split(); let buf = buf.as_mut().unwrap_left(); (ring, buf, pos, active) } #[inline(always)] fn split_with_statx(self: Pin<&mut Self>) -> (Pin<&mut Ring>, &mut libc::statx, &mut u64, &mut Op) { let (ring, buf, pos, active) = self.split(); if buf.is_left() { *buf = Either::Right(Box::new(unsafe { mem::zeroed() })); } let statx = buf.as_mut().unwrap_right(); (ring, statx, pos, active) } #[inline(always)] fn ring(self: Pin<&mut Self>) -> Pin<&mut Ring> { self.split().0 } #[inline(always)] fn buf(self: Pin<&mut Self>) -> &mut Buffer { self.split_with_buf().1 } #[inline(always)] fn pos(self: Pin<&mut Self>) -> &mut u64 { self.split().2 } fn confirm_close(self: Pin<&mut Self>) { *self.split().3 = Op::Closed; } } impl AsyncRead for File { fn poll_read(mut self: Pin<&mut Self>, ctx: &mut Context<'_>, buf: &mut [u8]) -> Poll> { let mut inner = ready!(self.as_mut().poll_fill_buf(ctx))?; let len = io::Read::read(&mut inner, buf)?; self.consume(len); Poll::Ready(Ok(len)) } } impl AsyncBufRead for File { fn poll_fill_buf(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { self.as_mut().guard_op(Op::Read); let fd = self.fd; let (ring, buf, pos, ..) = self.split_with_buf(); buf.fill_buf(|buf| { let n = ready!(ring.poll(ctx, 1, |sqs| { let mut sqe = sqs.single().unwrap(); unsafe { sqe.prep_read(fd, buf, *pos); } sqe }))?; *pos += n as u64; Poll::Ready(Ok(n as u32)) }) } fn consume(self: Pin<&mut Self>, amt: usize) { self.buf().consume(amt); } } impl AsyncWrite for File { fn poll_write(mut self: Pin<&mut Self>, ctx: &mut Context<'_>, slice: &[u8]) -> Poll> { self.as_mut().guard_op(Op::Write); let fd = self.fd; let (ring, buf, pos, ..) = self.split_with_buf(); let data = ready!(buf.fill_buf(|mut buf| { Poll::Ready(Ok(io::Write::write(&mut buf, slice)? as u32)) }))?; let n = ready!(ring.poll(ctx, 1, |sqs| { let mut sqe = sqs.single().unwrap(); unsafe { sqe.prep_write(fd, data, *pos); } sqe }))?; *pos += n as u64; buf.clear(); Poll::Ready(Ok(n as usize)) } fn poll_flush(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { ready!(self.poll_write(ctx, &[]))?; Poll::Ready(Ok(())) } fn poll_close(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { self.as_mut().guard_op(Op::Close); let fd = self.fd; ready!(self.as_mut().ring().poll(ctx, 1, |sqs| { let mut sqe = sqs.single().unwrap(); unsafe { sqe.prep_close(fd); } sqe }))?; self.confirm_close(); Poll::Ready(Ok(())) } } impl AsyncSeek for File { fn poll_seek(mut self: Pin<&mut Self>, ctx: &mut Context, pos: io::SeekFrom) -> Poll> { let (whence, offset) = match pos { io::SeekFrom::Start(n) => { *self.as_mut().pos() = n; return Poll::Ready(Ok(self.pos)); } io::SeekFrom::Current(n) => (self.pos, n), io::SeekFrom::End(n) => { (ready!(self.as_mut().poll_file_size(ctx))?, n) } }; let valid_seek = if offset.is_negative() { match whence.checked_sub(offset.abs() as u64) { Some(valid_seek) => valid_seek, None => { let invalid = io::Error::from(io::ErrorKind::InvalidInput); return Poll::Ready(Err(invalid)); } } } else { match whence.checked_add(offset as u64) { Some(valid_seek) => valid_seek, None => { let overflow = io::Error::from_raw_os_error(libc::EOVERFLOW); return Poll::Ready(Err(overflow)); } } }; *self.as_mut().pos() = valid_seek; Poll::Ready(Ok(self.pos)) } } impl From for File { fn from(file: fs::File) -> File { File::run_on_driver(file, DemoDriver::default()) } } impl From> for fs::File { fn from(mut file: File) -> fs::File { file.cancel(); let file = ManuallyDrop::new(file); unsafe { fs::File::from_raw_fd(file.fd) } } } impl Drop for File { fn drop(&mut self) { match self.active { Op::Closed => { } Op::Nothing => unsafe { libc::close(self.fd); }, _ => self.cancel(), } } } /// A future representing an opening file. pub struct Open(Submission); impl Open { fn inner(self: Pin<&mut Self>) -> Pin<&mut Submission> { unsafe { Pin::map_unchecked_mut(self, |this| &mut this.0) } } } impl Future for Open { type Output = io::Result>; fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll>> { let mut inner = self.inner(); let (_, result) = ready!(inner.as_mut().poll(ctx)); let fd = result? as i32; let driver = inner.driver().clone(); Poll::Ready(Ok(File::from_fd(fd, driver))) } } /// A future representing a file being created. pub struct Create(Submission); impl Create { fn inner(self: Pin<&mut Self>) -> Pin<&mut Submission> { unsafe { Pin::map_unchecked_mut(self, |this| &mut this.0) } } } impl Future for Create { type Output = io::Result>; fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll>> { let mut inner = self.inner(); let (_, result) = ready!(inner.as_mut().poll(ctx)); let fd = result? as i32; let driver = inner.driver().clone(); Poll::Ready(Ok(File::from_fd(fd, driver))) } } ================================================ FILE: src/io.rs ================================================ use std::borrow::Cow; use std::future::Future; use std::io; use std::os::unix::io::{AsRawFd, RawFd}; use std::pin::Pin; use std::task::{Poll, Context}; use futures_core::ready; use futures_io::AsyncWrite; use crate::buf::Buffer; use crate::{Drive, ring::Ring}; use crate::drive::demo::DemoDriver; #[macro_export] macro_rules! print { ($driver:expr, $($arg:tt)*) => {{ let mut s = format!($($arg)*); $crate::io::__print($driver, s.into_bytes()) }}; } #[macro_export] macro_rules! println { ($driver:expr) => {$crate::io::__print($driver, b"\n")}; ($driver:expr, $($arg:tt)*) => {{ let mut s = format!($($arg)*); s.push('\n'); $crate::io::__print($driver, s.into_bytes()) }}; } #[macro_export] macro_rules! eprint { ($driver:expr, $($arg:tt)*) => {{ let mut s = format!($($arg)*); $crate::io::__eprint($driver, s.into_bytes()) }}; } #[macro_export] macro_rules! eprintln { ($driver:expr) => {$crate::io::__eprint($driver, b"\n")}; ($driver:expr, $($arg:tt)*) => {{ let mut s = format!($($arg)*); s.push('\n'); $crate::io::__eprint($driver, s.into_bytes()) }}; } #[doc(hidden)] pub async fn __print(driver: D, bytes: impl Into>) { Print { ring: Ring::new(driver), fd: 1, bytes: bytes.into(), idx: 0, }.await.expect("printing to stdout failed") } #[doc(hidden)] pub async fn __eprint(driver: D, bytes: impl Into>) { Print { ring: Ring::new(driver), fd: 2, bytes: bytes.into(), idx: 0, }.await.expect("printing to stderr failed") } struct Print { ring: Ring, fd: RawFd, bytes: Cow<'static, [u8]>, idx: usize, } impl Print { fn split(self: Pin<&mut Self>) -> (Pin<&mut Ring>, RawFd, &[u8], &mut usize) { unsafe { let this = Pin::get_unchecked_mut(self); let bytes = &this.bytes.as_ref()[this.idx..]; (Pin::new_unchecked(&mut this.ring), this.fd, bytes, &mut this.idx) } } } impl Future for Print { type Output = io::Result<()>; fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll { let (mut ring, fd, mut bytes, idx) = self.split(); if !bytes.is_empty() { loop { let written = ready!(ring.as_mut().poll(ctx, 1, |sqs| unsafe { let mut sqe = sqs.single().unwrap(); sqe.prep_write(fd, bytes, 0); sqe }))? as usize; *idx += written; if written == bytes.len() { return Poll::Ready(Ok(())); } else { bytes = &bytes[written..]; } } } else { return Poll::Ready(Ok(())); } } } /// A handle to the standard output of the current process. pub struct Stdout { ring: Ring, buf: Buffer, } /// Constructs a new `stdout` handle run on the demo driver. /// ```no_run /// use ringbahn::io; /// /// # use futures::AsyncWriteExt; /// # fn main() -> std::io::Result<()> { futures::executor::block_on(async { /// io::stdout().write(b"hello, world").await?; /// # Ok(()) /// # }) /// # } /// ``` // TODO synchronization note? pub fn stdout() -> Stdout { stdout_on_driver(DemoDriver::default()) } /// Constructs a new `stdout` handle run on the provided driver. pub fn stdout_on_driver(driver: D) -> Stdout { Stdout { ring: Ring::new(driver), buf: Buffer::default(), } } impl Stdout { #[inline(always)] fn split(self: Pin<&mut Self>) -> (Pin<&mut Ring>, &mut Buffer) { unsafe { let this = Pin::get_unchecked_mut(self); (Pin::new_unchecked(&mut this.ring), &mut this.buf) } } } impl AsyncWrite for Stdout { fn poll_write(self: Pin<&mut Self>, ctx: &mut Context<'_>, slice: &[u8]) -> Poll> { let fd = self.as_raw_fd(); let (ring, buf, ..) = self.split(); let data = ready!(buf.fill_buf(|mut buf| { Poll::Ready(Ok(io::Write::write(&mut buf, slice)? as u32)) }))?; let n = ready!(ring.poll(ctx, 1, |sqs| { let mut sqe = sqs.single().unwrap(); unsafe { sqe.prep_write(fd, data, 0); } sqe }))?; buf.clear(); Poll::Ready(Ok(n as usize)) } fn poll_flush(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { ready!(self.poll_write(ctx, &[]))?; Poll::Ready(Ok(())) } fn poll_close(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { self.poll_flush(ctx) } } impl AsRawFd for Stdout { fn as_raw_fd(&self) -> RawFd { libc::STDOUT_FILENO } } ================================================ FILE: src/lib.rs ================================================ pub mod fs; pub mod net; pub mod unix; pub mod drive; pub mod event; pub mod ring; pub mod io; mod buf; mod submission; pub use submission::Submission; #[doc(inline)] pub use drive::Drive; #[doc(inline)] pub use event::Event; ================================================ FILE: src/net/listener.rs ================================================ use std::io; use std::future::Future; use std::net::{ToSocketAddrs, SocketAddr}; use std::os::unix::io::{RawFd}; use std::pin::Pin; use std::task::{Context, Poll}; use futures_core::{ready, Stream}; use iou::sqe::SockAddrStorage; use nix::sys::socket::{self as nix_socket, SockProtocol, SockFlag}; use crate::drive::{Drive, demo::DemoDriver}; use crate::ring::{Cancellation, Ring}; use super::TcpStream; pub struct TcpListener { ring: Ring, fd: RawFd, active: Op, addr: Option>, } #[derive(Eq, PartialEq, Copy, Clone, Debug)] enum Op { Nothing = 0, Accept, Close, Closed, } impl TcpListener { pub fn bind(addr: A) -> io::Result { TcpListener::bind_on_driver(addr, DemoDriver::default()) } } impl TcpListener { pub fn bind_on_driver(addr: A, driver: D) -> io::Result> { let (fd, addr) = super::socket(addr, SockProtocol::Tcp)?; let addr = iou::sqe::SockAddr::Inet(nix_socket::InetAddr::from_std(&addr)); nix_socket::setsockopt(fd, nix_socket::sockopt::ReuseAddr, &true) .map_err(|e| e.as_errno().unwrap_or(nix::errno::Errno::EIO))?; nix_socket::bind(fd, &addr).map_err(|e| e.as_errno().unwrap_or(nix::errno::Errno::EIO))?; nix_socket::listen(fd, 128).map_err(|e| e.as_errno().unwrap_or(nix::errno::Errno::EIO))?; let ring = Ring::new(driver); Ok(TcpListener { active: Op::Nothing, addr: None, fd, ring, }) } pub fn close(&mut self) -> Close where D: Unpin { Pin::new(self).close_pinned() } pub fn close_pinned(self: Pin<&mut Self>) -> Close { Close { socket: self } } fn guard_op(self: Pin<&mut Self>, op: Op) { let (ring, addr, active) = self.split(); if *active == Op::Closed { panic!("Attempted to perform IO on a closed TcpListener"); } else if *active != Op::Nothing && *active != op { ring.cancel_pinned(Cancellation::from(addr.take())); } *active = op; } fn cancel(&mut self) { let cancellation = match self.active { Op::Accept => Cancellation::from(self.addr.take()), Op::Close => Cancellation::from(()), Op::Closed => return, Op::Nothing => return, }; self.active = Op::Nothing; self.ring.cancel(cancellation); } fn drop_addr(self: Pin<&mut Self>) { self.split().1.take(); } fn ring(self: Pin<&mut Self>) -> Pin<&mut Ring> { self.split().0 } fn split(self: Pin<&mut Self>) -> (Pin<&mut Ring>, &mut Option>, &mut Op) { unsafe { let this = Pin::get_unchecked_mut(self); (Pin::new_unchecked(&mut this.ring), &mut this.addr, &mut this.active) } } fn split_with_addr(self: Pin<&mut Self>) -> (Pin<&mut Ring>, &mut SockAddrStorage, &mut Op) { let (ring, addr, active) = self.split(); if addr.is_none() { *addr = Some(Box::new(SockAddrStorage::uninit())); } (ring, &mut **addr.as_mut().unwrap(), active) } fn confirm_close(self: Pin<&mut Self>) { *self.split().2 = Op::Closed; } } impl TcpListener { pub fn accept(&mut self) -> Accept<'_, D> where D: Unpin { Pin::new(self).accept_pinned() } pub fn accept_pinned(self: Pin<&mut Self>) -> Accept<'_, D> { Accept { socket: self } } pub fn incoming(&mut self) -> Incoming<'_, D> where D: Unpin { Pin::new(self).incoming_pinned() } pub fn incoming_pinned(self: Pin<&mut Self>) -> Incoming<'_, D> { Incoming { accept: self.accept_pinned() } } pub fn accept_no_addr(&mut self) -> AcceptNoAddr<'_, D> where D: Unpin { Pin::new(self).accept_no_addr_pinned() } pub fn accept_no_addr_pinned(self: Pin<&mut Self>) -> AcceptNoAddr<'_, D> { AcceptNoAddr { socket: self } } pub fn incoming_no_addr(&mut self) -> IncomingNoAddr<'_, D> where D: Unpin { Pin::new(self).incoming_no_addr_pinned() } pub fn incoming_no_addr_pinned(self: Pin<&mut Self>) -> IncomingNoAddr<'_, D> { IncomingNoAddr { accept: self.accept_no_addr_pinned() } } pub fn poll_accept(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll, SocketAddr)>> { self.as_mut().guard_op(Op::Accept); let fd = self.fd; let (ring, addr, ..) = self.as_mut().split_with_addr(); let fd = ready!(ring.poll(ctx, 1, |sqs| { let mut sqe = sqs.single().unwrap(); unsafe { sqe.prep_accept(fd, Some(addr), SockFlag::empty()); } sqe }))? as RawFd; let addr = { let result = unsafe { addr.as_socket_addr() }; self.as_mut().drop_addr(); match result? { iou::sqe::SockAddr::Inet(addr) => addr.to_std(), addr => panic!("TcpListener addr cannot be {:?}", addr.family()), } }; Poll::Ready(Ok((TcpStream::from_fd(fd, self.ring().clone()), addr))) } pub fn poll_accept_no_addr(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll>> { self.as_mut().guard_op(Op::Accept); let fd = self.fd; let fd = ready!(self.as_mut().ring().poll(ctx, 1, |sqs| { let mut sqe = sqs.single().unwrap(); unsafe { sqe.prep_accept(fd, None, SockFlag::empty()); } sqe }))? as RawFd; Poll::Ready(Ok(TcpStream::from_fd(fd, self.ring().clone()))) } } impl Drop for TcpListener { fn drop(&mut self) { match self.active { Op::Closed => { } Op::Nothing => unsafe { libc::close(self.fd); } _ => self.cancel(), } } } pub struct Accept<'a, D: Drive> { socket: Pin<&'a mut TcpListener>, } impl<'a, D: Drive + Clone> Future for Accept<'a, D> { type Output = io::Result<(TcpStream, SocketAddr)>; fn poll(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll { self.socket.as_mut().poll_accept(ctx) } } pub struct AcceptNoAddr<'a, D: Drive> { socket: Pin<&'a mut TcpListener>, } impl<'a, D: Drive + Clone> Future for AcceptNoAddr<'a, D> { type Output = io::Result>; fn poll(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll { self.socket.as_mut().poll_accept_no_addr(ctx) } } pub struct Incoming<'a, D: Drive> { accept: Accept<'a, D>, } impl<'a, D: Drive> Incoming<'a, D> { fn inner(self: Pin<&mut Self>) -> Pin<&mut Accept<'a, D>> { unsafe { Pin::map_unchecked_mut(self, |this| &mut this.accept) } } } impl<'a, D: Drive + Clone> Stream for Incoming<'a, D> { type Item = io::Result<(TcpStream, SocketAddr)>; fn poll_next(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { let next = ready!(self.inner().poll(ctx)); Poll::Ready(Some(next)) } } pub struct IncomingNoAddr<'a, D: Drive> { accept: AcceptNoAddr<'a, D>, } impl<'a, D: Drive> IncomingNoAddr<'a, D> { fn inner(self: Pin<&mut Self>) -> Pin<&mut AcceptNoAddr<'a, D>> { unsafe { Pin::map_unchecked_mut(self, |this| &mut this.accept) } } } impl<'a, D: Drive + Clone> Stream for IncomingNoAddr<'a, D> { type Item = io::Result>; fn poll_next(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { let next = ready!(self.inner().poll(ctx)); Poll::Ready(Some(next)) } } pub struct Close<'a, D: Drive> { socket: Pin<&'a mut TcpListener>, } impl<'a, D: Drive> Future for Close<'a, D> { type Output = io::Result<()>; fn poll(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { self.socket.as_mut().guard_op(Op::Close); let fd = self.socket.fd; ready!(self.socket.as_mut().ring().poll(ctx, 1, |sqs| { let mut sqe = sqs.single().unwrap(); unsafe { sqe.prep_close(fd); } sqe }))?; self.socket.as_mut().confirm_close(); Poll::Ready(Ok(())) } } ================================================ FILE: src/net/mod.rs ================================================ mod listener; mod stream; use std::io; use std::net::{SocketAddr, ToSocketAddrs}; use std::os::unix::io::RawFd; pub use listener::{TcpListener, Accept, AcceptNoAddr, Close, Incoming, IncomingNoAddr}; pub use stream::{TcpStream, Connect}; use nix::sys::socket as nix; fn socket(addr: A, protocol: nix::SockProtocol) -> io::Result<(RawFd, SocketAddr)> { use io::{Error, ErrorKind}; let mut error = Error::new(ErrorKind::InvalidInput, "could not resolve to any addresses"); for addr in addr.to_socket_addrs()? { let domain = match addr.is_ipv6() { true => nix::AddressFamily::Inet6, false => nix::AddressFamily::Inet, }; let flags = nix::SockFlag::SOCK_CLOEXEC; match nix::socket(domain, nix::SockType::Stream, flags, Some(protocol)) { Ok(fd) => return Ok((fd, addr)), _ => error = io::Error::last_os_error(), } } Err(error) } ================================================ FILE: src/net/stream.rs ================================================ use std::io; use std::future::Future; use std::net::ToSocketAddrs; use std::os::unix::io::RawFd; use std::pin::Pin; use std::task::{Context, Poll}; use futures_core::ready; use futures_io::{AsyncRead, AsyncBufRead, AsyncWrite}; use iou::sqe::SockAddr; use nix::sys::socket::SockProtocol; use crate::buf::Buffer; use crate::drive::{Drive, demo::DemoDriver}; use crate::ring::Ring; use crate::event; use crate::Submission; use super::socket; pub struct TcpStream { ring: Ring, buf: Buffer, active: Op, fd: RawFd, } #[derive(Copy, Clone, Debug, Eq, PartialEq)] enum Op { Read, Write, Close, Nothing, Closed, } impl TcpStream { pub fn connect(addr: A) -> Connect { TcpStream::connect_on_driver(addr, DemoDriver::default()) } } impl TcpStream { pub fn connect_on_driver(addr: A, driver: D) -> Connect { let (fd, addr) = match socket(addr, SockProtocol::Tcp) { Ok(fd) => fd, Err(e) => return Connect(Err(Some(e))), }; let addr = Box::new(SockAddr::Inet(nix::sys::socket::InetAddr::from_std(&addr))); Connect(Ok(driver.submit(event::Connect { fd, addr }))) } } impl TcpStream { pub(crate) fn from_fd(fd: RawFd, ring: Ring) -> TcpStream { TcpStream { buf: Buffer::default(), active: Op::Nothing, fd, ring, } } fn guard_op(self: Pin<&mut Self>, op: Op) { let (ring, buf, active) = self.split(); if *active == Op::Closed { panic!("Attempted to perform IO on a closed stream"); } else if *active != Op::Nothing && *active != op { ring.cancel_pinned(buf.cancellation()); } *active = op; } fn cancel(&mut self) { self.active = Op::Nothing; self.ring.cancel(self.buf.cancellation()); } #[inline(always)] fn ring(self: Pin<&mut Self>) -> Pin<&mut Ring> { self.split().0 } #[inline(always)] fn buf(self: Pin<&mut Self>) -> &mut Buffer { self.split().1 } #[inline(always)] fn split(self: Pin<&mut Self>) -> (Pin<&mut Ring>, &mut Buffer, &mut Op) { unsafe { let this = Pin::get_unchecked_mut(self); (Pin::new_unchecked(&mut this.ring), &mut this.buf, &mut this.active) } } fn confirm_close(self: Pin<&mut Self>) { *self.split().2 = Op::Closed; } } pub struct Connect( Result, Option> ); impl Future for Connect { type Output = io::Result>; fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll { match self.project() { Ok(mut submission) => { let (connect, result) = ready!(submission.as_mut().poll(ctx)); result?; let driver = submission.driver().clone(); Poll::Ready(Ok(TcpStream::from_fd(connect.fd, Ring::new(driver)))) } Err(err) => { let err = err.take().expect("polled Connect future after completion"); Poll::Ready(Err(err)) } } } } impl Connect { fn project(self: Pin<&mut Self>) -> Result>, &mut Option> { unsafe { match &mut Pin::get_unchecked_mut(self).0 { Ok(submission) => Ok(Pin::new_unchecked(submission)), Err(err) => Err(err) } } } } impl AsyncRead for TcpStream { fn poll_read(mut self: Pin<&mut Self>, ctx: &mut Context<'_>, buf: &mut [u8]) -> Poll> { let mut inner = ready!(self.as_mut().poll_fill_buf(ctx))?; let len = io::Read::read(&mut inner, buf)?; self.consume(len); Poll::Ready(Ok(len)) } } impl AsyncBufRead for TcpStream { fn poll_fill_buf(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { self.as_mut().guard_op(Op::Read); let fd = self.fd; let (ring, buf, ..) = self.split(); buf.fill_buf(|buf| { let n = ready!(ring.poll(ctx, 1, |sqs| { let mut sqe = sqs.single().unwrap(); unsafe { sqe.prep_read(fd, buf, 0); } sqe }))?; Poll::Ready(Ok(n as u32)) }) } fn consume(self: Pin<&mut Self>, amt: usize) { self.buf().consume(amt); } } impl AsyncWrite for TcpStream { fn poll_write(mut self: Pin<&mut Self>, ctx: &mut Context<'_>, slice: &[u8]) -> Poll> { self.as_mut().guard_op(Op::Write); let fd = self.fd; let (ring, buf, ..) = self.split(); let data = ready!(buf.fill_buf(|mut buf| { Poll::Ready(Ok(io::Write::write(&mut buf, slice)? as u32)) }))?; let n = ready!(ring.poll(ctx, 1, |sqs| { let mut sqe = sqs.single().unwrap(); unsafe { sqe.prep_write(fd, data, 0); } sqe }))?; buf.clear(); Poll::Ready(Ok(n as usize)) } fn poll_flush(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { ready!(self.poll_write(ctx, &[]))?; Poll::Ready(Ok(())) } fn poll_close(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { self.as_mut().guard_op(Op::Close); let fd = self.fd; ready!(self.as_mut().ring().poll(ctx, 1, |sqs| { let mut sqe = sqs.single().unwrap(); unsafe { sqe.prep_close(fd); } sqe }))?; self.confirm_close(); Poll::Ready(Ok(())) } } impl Drop for TcpStream { fn drop(&mut self) { match self.active { Op::Closed => { } Op::Nothing => unsafe { libc::close(self.fd); }, _ => self.cancel(), } } } ================================================ FILE: src/ring/cancellation.rs ================================================ use std::any::Any; use std::ffi::CString; use std::mem; use std::ptr; use either::Either; use crate::buf::Buffer; /// A cancellation callback to clean up resources when IO gets cancelled. /// /// When a user cancels interest in an event, the future representing that event needs to be /// dropped. However, that future may share ownership of some data structures (like buffers) /// with the kernel, which is completing the event. The cancellation callback will take /// ownership of those resources, and clean them up when it is dropped. pub struct Cancellation { data: *mut (), metadata: usize, drop: unsafe fn(*mut (), usize), } pub unsafe trait Cancel { fn into_raw(self) -> (*mut (), usize); unsafe fn drop_raw(data: *mut (), metadata: usize); } unsafe impl Cancel for Box { fn into_raw(self) -> (*mut (), usize) { (Box::into_raw(self) as *mut (), 0) } unsafe fn drop_raw(data: *mut (), _: usize) { drop(Box::from_raw(data as *mut T)); } } unsafe impl Cancel for Box<[T]> { fn into_raw(self) -> (*mut (), usize) { let len = self.len(); (Box::into_raw(self) as *mut (), len) } unsafe fn drop_raw(data: *mut (), len: usize) { drop(Vec::from_raw_parts(data as *mut T, len, len).into_boxed_slice()); } } #[repr(C)] struct TraitObject { data: *mut (), vtable: *mut (), } unsafe impl Cancel for Box { fn into_raw(self) -> (*mut (), usize) { let obj = unsafe { mem::transmute::(self) }; (obj.data, obj.vtable as usize) } unsafe fn drop_raw(data: *mut (), metadata: usize) { let obj = TraitObject { data, vtable: metadata as *mut () }; drop(mem::transmute::(obj)); } } unsafe impl Cancel for iou::registrar::RegisteredBuf { fn into_raw(self) -> (*mut (), usize) { self.into_inner().into_raw() } unsafe fn drop_raw(data: *mut (), metadata: usize) { Box::<[u8]>::drop_raw(data, metadata) } } unsafe impl Cancel for CString { fn into_raw(self) -> (*mut (), usize) { (self.into_raw() as *mut (), 0) } unsafe fn drop_raw(data: *mut (), _: usize) { drop(CString::from_raw(data as *mut libc::c_char)); } } unsafe impl Cancel for () { fn into_raw(self) -> (*mut (), usize) { (ptr::null_mut(), 0) } unsafe fn drop_raw(_: *mut (), _: usize) { } } pub unsafe trait CancelNarrow: Cancel { } unsafe impl CancelNarrow for Box { } unsafe impl CancelNarrow for CString { } unsafe impl Cancel for (T, U) { fn into_raw(self) -> (*mut (), usize) { let left = self.0.into_raw().0; let right = self.1.into_raw().0; (left, right as usize) } unsafe fn drop_raw(data: *mut (), metadata: usize) { T::drop_raw(data, 0); U::drop_raw(metadata as *mut (), 0); } } impl Cancellation { fn new(object: T) -> Cancellation { let (data, metadata) = object.into_raw(); Cancellation { data, metadata, drop: T::drop_raw } } } impl From for Cancellation { fn from(object: T) -> Cancellation { Cancellation::new(object) } } impl From> for Cancellation where Cancellation: From { fn from(object: Option) -> Cancellation { object.map_or(Cancellation::new(()), Cancellation::from) } } impl From for Cancellation { fn from(buffer: Buffer) -> Cancellation { Cancellation::from(buffer.into_boxed_slice()) } } impl From> for Cancellation where Cancellation: From + From { fn from(object: Either) -> Cancellation { object.either(Cancellation::from, Cancellation::from) } } unsafe impl Send for Cancellation { } unsafe impl Sync for Cancellation { } impl Drop for Cancellation { fn drop(&mut self) { unsafe { (self.drop)(self.data, self.metadata) } } } ================================================ FILE: src/ring/completion.rs ================================================ use std::io; use std::mem::{self, ManuallyDrop}; use std::task::Waker; use parking_lot::Mutex; use crate::ring::Cancellation; use iou::CQE; use State::*; /// A completion tracks an event that has been submitted to io-uring. It is a pointer to a heap /// allocated object which represents the state of the event's completion. Ownership of this object /// is shared between the Completion type and the io-uring instance (the address of the object is /// passed as a user_data field with the event's SQE). /// /// Therefore, it requires a fair amout of unsafe code and synchronization to properly manage the /// lifecycle of this object. That code is encapsulated here inside a safe API for the rest of /// ringbahn to use. /// /// This API is not publicly visible outside of this crate. (The Completion type in the public API /// is an opaque wrapper aroud this type). End users do not need to understand the completion API. pub struct Completion { state: ManuallyDrop>>, } enum State { Submitted(Waker), Completed(io::Result), Cancelled(Cancellation), Empty, } impl Completion { /// Create a new completion for an event being prepared. When the event is completed by /// io-uring, the waker this completion holds will be awoken. pub fn new(waker: Waker) -> Completion { Completion { state: ManuallyDrop::new(Box::new(Mutex::new(Submitted(waker)))), } } /// Get the address of this completion, so that it can set as the user_data field of the SQE /// being prepared. pub fn addr(&self) -> u64 { &**self.state as *const Mutex as usize as u64 } /// Check if the completion has completed. If it has, the result of the completion will be /// returned and the completion will be deallocated. If it has not been completed, the waker /// field will be updated to the new waker if the old waker would not wake the same task. pub fn check(self, waker: &Waker) -> Result, Completion> { let mut state = self.state.lock(); match mem::replace(&mut *state, State::Empty) { Submitted(old_waker) => { let waker = if old_waker.will_wake(waker) { old_waker } else { waker.clone() }; *state = Submitted(waker); drop(state); Err(self) } Completed(result) => { drop(state); drop(ManuallyDrop::into_inner(self.state)); Ok(result) } _ => unreachable!() } } /// Cancel interest in this completion. The Cancellation callback will be stored to clean up /// resources shared with the kernel when the event completes. pub fn cancel(self, callback: Cancellation) { let mut state = self.state.lock(); match &*state { Submitted(_) => { *state = Cancelled(callback); drop(state); } Completed(_) => { drop(callback); drop(state); drop(ManuallyDrop::into_inner(self.state)); } _ => unreachable!() } } fn complete(self, result: io::Result) { let mut state = self.state.lock(); match mem::replace(&mut *state, State::Empty) { Submitted(waker) => { *state = Completed(result); waker.wake(); } Cancelled(callback) => { drop(callback); drop(state); drop(ManuallyDrop::into_inner(self.state)); } _ => unreachable!() } } } pub fn complete(cqe: CQE) { unsafe { let result = cqe.result(); let user_data = cqe.user_data(); // iou should never raise LIBURING_UDATA_TIMEOUTs, this is just to catch bugs in iou debug_assert!(user_data != uring_sys::LIBURING_UDATA_TIMEOUT); let state = user_data as *mut Mutex; if !state.is_null() { let completion = Completion { state: ManuallyDrop::new(Box::from_raw(state)) }; completion.complete(result); } }; } ================================================ FILE: src/ring/mod.rs ================================================ mod cancellation; pub(crate) mod completion; use std::io; use std::mem; use std::pin::Pin; use std::task::{Context, Poll}; use futures_core::ready; use iou::{SQE, SQEs}; use crate::drive::{self, Drive}; pub use cancellation::{Cancellation, Cancel, CancelNarrow}; pub(crate) use completion::Completion; use State::*; /// A low-level primitive for building an IO object on io-uring /// /// Ring is a state machine similar to `Submission`, but it is designed to cycle through multiple /// IO events submitted to io-uring, rather than representing a single submission. Because of this, /// it is more low level, but it is suitable for building an IO object like a `File` on top of /// io-uring. /// /// Users writing code on top of `Ring` are responsible for making sure that it is correct. For /// example, when calling `poll`, users must ensure that they are in the proper state to submit /// whatever type of IO they would be attempting to submit. Additionally, users should note that /// `Ring` does not implement `Drop`. In order to cancel any ongoing IO, users are responsible for /// implementing drop to call cancel properly. pub struct Ring { state: State, driver: D, } enum State { Inert, Prepared(Completion), Submitted(Completion), Cancelled(u64), Lost, } impl Default for Ring { fn default() -> Ring { Ring::new(D::default()) } } impl Clone for Ring { fn clone(&self) -> Ring { Ring::new(self.driver.clone()) } } impl Ring { /// Construct a new Ring on top of a driver. #[inline(always)] pub fn new(driver: D) -> Ring { Ring { state: Inert, driver } } pub fn driver(&self) -> &D { &self.driver } /// Poll the ring state machine. /// /// This accepts a callback, `prepare`, which prepares an event to be submitted to io-uring. /// This callback will only be called once during an iteration of ring's state machine: once an /// event has been prepared, until it is completed or cancelled, a single ring instance will /// not prepare any additional events. #[inline] pub fn poll( mut self: Pin<&mut Self>, ctx: &mut Context<'_>, count: u32, prepare: impl for<'sq> FnOnce(&mut SQEs<'sq>) -> SQE<'sq>, ) -> Poll> { match self.state { Inert | Cancelled(_) => { ready!(self.as_mut().poll_prepare(ctx, count, prepare)); ready!(self.as_mut().poll_submit(ctx)); Poll::Pending } Prepared(_) => { match self.as_mut().poll_complete(ctx) { ready @ Poll::Ready(..) => ready, Poll::Pending => { ready!(self.poll_submit(ctx)); Poll::Pending } } } Submitted(_) => self.poll_complete(ctx), Lost => panic!("Ring in a bad state; driver is faulty"), } } #[inline(always)] fn poll_prepare( self: Pin<&mut Self>, ctx: &mut Context<'_>, count: u32, prepare: impl for<'sq> FnOnce(&mut SQEs<'sq>) -> SQE<'sq>, ) -> Poll<()> { let (driver, state) = self.split(); let completion = match *state { Cancelled(prev) => { ready!(driver.poll_prepare(ctx, count + 1, |mut sqs, ctx| { *state = Lost; unsafe { sqs.hard_linked().next().unwrap().prep_cancel(prev, 0); } let sqe = prepare(&mut sqs); drive::Completion::new(sqe, sqs, ctx) })) } Inert => { ready!(driver.poll_prepare(ctx, count, |mut sqs, ctx| { *state = Lost; let sqe = prepare(&mut sqs); drive::Completion::new(sqe, sqs, ctx) })) } _ => unreachable!(), }; *state = Prepared(completion.real); Poll::Ready(()) } #[inline(always)] fn poll_submit(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<()> { let (driver, state) = self.split(); // TODO figure out how to handle this result let _ = ready!(driver.poll_submit(ctx)); if let Prepared(completion) | Submitted(completion) = mem::replace(state, Lost) { *state = Submitted(completion); Poll::Ready(()) } else { unreachable!() } } #[inline(always)] fn poll_complete(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { let (_, state) = self.split(); match mem::replace(state, Lost) { Prepared(completion) => { match completion.check(ctx.waker()) { Ok(result) => { *state = Inert; Poll::Ready(result) } Err(completion) => { *state = Prepared(completion); Poll::Pending } } } Submitted(completion) => { match completion.check(ctx.waker()) { Ok(result) => { *state = Inert; Poll::Ready(result) } Err(completion) => { *state = Submitted(completion); Poll::Pending } } } _ => unreachable!(), } } /// Cancel any ongoing IO with this cancellation. /// /// Users are responsible for ensuring that the cancellation passed would be appropriate to /// clean up the resources of the running event. #[inline] pub fn cancel(&mut self, cancellation: Cancellation) { self.state.cancel(cancellation); } /// Cancel any ongoing IO, but from a pinned reference. /// /// This has the same behavior of as Ring::cancel. pub fn cancel_pinned(self: Pin<&mut Self>, cancellation: Cancellation) { self.split().1.cancel(cancellation); } fn split(self: Pin<&mut Self>) -> (Pin<&mut D>, &mut State) { unsafe { let this = Pin::get_unchecked_mut(self); (Pin::new_unchecked(&mut this.driver), &mut this.state) } } } impl State { fn cancel(&mut self, cancellation: Cancellation) { match mem::replace(self, Lost) { Prepared(completion) | Submitted(completion) => { *self = Cancelled(completion.addr()); completion.cancel(cancellation); } state => { *self = state; } } } } ================================================ FILE: src/submission.rs ================================================ use std::future::Future; use std::io; use std::mem::ManuallyDrop; use std::pin::Pin; use std::task::{Context, Poll}; use futures_core::ready; use crate::{Event, Drive, ring::Ring}; /// A [`Future`] representing an event submitted to io-uring pub struct Submission { ring: Ring, event: Option, } impl Submission { /// Construct a new submission from an event and a driver. pub fn new(event: E, driver: D) -> Submission { Submission { ring: Ring::new(driver), event: Some(event), } } /// Access the driver this submission is using pub fn driver(&self) -> &D { self.ring.driver() } pub fn replace_event(self: Pin<&mut Self>, event: E) { let (ring, event_slot) = self.split(); if let Some(event) = event_slot.take() { ring.cancel_pinned(E::cancel(ManuallyDrop::new(event))) } *event_slot = Some(event); } fn split(self: Pin<&mut Self>) -> (Pin<&mut Ring>, &mut Option) { unsafe { let this = Pin::get_unchecked_mut(self); (Pin::new_unchecked(&mut this.ring), &mut this.event) } } } impl Future for Submission where E: Event, D: Drive, { type Output = (E, io::Result); fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll { let (ring, event) = self.split(); let result = if let Some(event) = event { let count = event.sqes_needed(); ready!(ring.poll(ctx, count, |sqs| unsafe { event.prepare(sqs) })) } else { panic!("polled Submission after completion") }; Poll::Ready((event.take().unwrap(), result)) } } impl Drop for Submission { fn drop(&mut self) { if let Some(event) = self.event.take() { self.ring.cancel(E::cancel(ManuallyDrop::new(event))) } } } ================================================ FILE: src/unix/listener.rs ================================================ use std::io; use std::future::Future; use std::os::unix::io::{RawFd}; use std::path::Path; use std::pin::Pin; use std::task::{Context, Poll}; use futures_core::{ready, Stream}; use nix::sys::socket::{self as nix_socket, SockFlag}; use crate::drive::{Drive, demo::DemoDriver}; use crate::ring::{Ring, Cancellation}; use super::UnixStream; pub struct UnixListener { ring: Ring, fd: RawFd, active: Op, } #[derive(Eq, PartialEq, Copy, Clone, Debug)] enum Op { Nothing = 0, Accept, Close, Closed, } impl UnixListener { pub fn bind(path: impl AsRef) -> io::Result { UnixListener::bind_on_driver(path, DemoDriver::default()) } } impl UnixListener { pub fn bind_on_driver(path: impl AsRef, driver: D) -> io::Result> { let fd = super::socket()?; let addr = iou::sqe::SockAddr::Unix(nix_socket::UnixAddr::new(path.as_ref()).unwrap()); nix_socket::bind(fd, &addr).map_err(|e| e.as_errno().unwrap_or(nix::errno::Errno::EIO))?; nix_socket::listen(fd, 128).map_err(|e| e.as_errno().unwrap_or(nix::errno::Errno::EIO))?; let ring = Ring::new(driver); Ok(UnixListener { active: Op::Nothing, fd, ring, }) } pub fn close(&mut self) -> Close where D: Unpin { Pin::new(self).close_pinned() } pub fn close_pinned(self: Pin<&mut Self>) -> Close { Close { socket: self } } fn guard_op(self: Pin<&mut Self>, op: Op) { let this = unsafe { Pin::get_unchecked_mut(self) }; if this.active == Op::Closed { panic!("Attempted to perform IO on a closed UnixListener"); } if this.active != Op::Nothing && this.active != op { this.cancel(); } this.active = op; } fn cancel(&mut self) { if !matches!(self.active, Op::Nothing | Op::Closed) { self.active = Op::Nothing; self.ring.cancel(Cancellation::from(())); } } fn ring(self: Pin<&mut Self>) -> Pin<&mut Ring> { unsafe { Pin::map_unchecked_mut(self, |this| &mut this.ring) } } fn confirm_close(self: Pin<&mut Self>) { unsafe { Pin::get_unchecked_mut(self).active = Op::Closed; } } } impl UnixListener { pub fn accept(&mut self) -> Accept<'_, D> where D: Unpin { Pin::new(self).accept_pinned() } pub fn accept_pinned(self: Pin<&mut Self>) -> Accept<'_, D> { Accept { socket: self } } pub fn incoming(&mut self) -> Incoming<'_, D> where D: Unpin { Pin::new(self).incoming_pinned() } pub fn incoming_pinned(self: Pin<&mut Self>) -> Incoming<'_, D> { Incoming { accept: self.accept_pinned() } } pub fn poll_accept(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll>> { self.as_mut().guard_op(Op::Accept); let fd = self.fd; let fd = ready!(self.as_mut().ring().poll(ctx, 1, |sqs| unsafe { let mut sqe = sqs.single().unwrap(); sqe.prep_accept(fd, None, SockFlag::empty()); sqe }))? as RawFd; Poll::Ready(Ok(UnixStream::from_fd(fd, self.ring().clone()))) } } impl Drop for UnixListener { fn drop(&mut self) { match self.active { Op::Closed => { } Op::Nothing => unsafe { libc::close(self.fd); } _ => self.cancel(), } } } pub struct Accept<'a, D: Drive> { socket: Pin<&'a mut UnixListener>, } impl<'a, D: Drive + Clone> Future for Accept<'a, D> { type Output = io::Result>; fn poll(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll { self.socket.as_mut().poll_accept(ctx) } } pub struct Incoming<'a, D: Drive> { accept: Accept<'a, D>, } impl<'a, D: Drive> Incoming<'a, D> { fn inner(self: Pin<&mut Self>) -> Pin<&mut Accept<'a, D>> { unsafe { Pin::map_unchecked_mut(self, |this| &mut this.accept) } } } impl<'a, D: Drive + Clone> Stream for Incoming<'a, D> { type Item = io::Result>; fn poll_next(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { let next = ready!(self.inner().poll(ctx)); Poll::Ready(Some(next)) } } pub struct Close<'a, D: Drive> { socket: Pin<&'a mut UnixListener>, } impl<'a, D: Drive> Future for Close<'a, D> { type Output = io::Result<()>; fn poll(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { self.socket.as_mut().guard_op(Op::Close); let fd = self.socket.fd; ready!(self.socket.as_mut().ring().poll(ctx, 1, |sqs| unsafe { let mut sqe = sqs.single().unwrap(); sqe.prep_close(fd); sqe }))?; self.socket.as_mut().confirm_close(); Poll::Ready(Ok(())) } } ================================================ FILE: src/unix/mod.rs ================================================ use std::io; use std::os::unix::io::RawFd; mod listener; mod stream; pub use listener::{UnixListener, Close, Accept, Incoming}; pub use stream::{UnixStream, Connect}; use nix::sys::socket as nix; fn socket() -> io::Result { match nix::socket(nix::AddressFamily::Unix, nix::SockType::Stream, nix::SockFlag::SOCK_CLOEXEC, None) { Ok(fd) => Ok(fd), Err(_) => Err(io::Error::last_os_error()), } } fn socketpair() -> io::Result<(RawFd, RawFd)> { match nix::socketpair(nix::AddressFamily::Unix, nix::SockType::Stream, None, nix::SockFlag::SOCK_CLOEXEC) { Ok((fd1, fd2)) => Ok((fd1, fd2)), Err(_) => Err(io::Error::last_os_error()), } } ================================================ FILE: src/unix/stream.rs ================================================ use std::io; use std::future::Future; use std::os::unix::io::RawFd; use std::path::Path; use std::pin::Pin; use std::task::{Context, Poll}; use futures_core::ready; use futures_io::{AsyncRead, AsyncBufRead, AsyncWrite}; use iou::sqe::SockAddr; use nix::sys::socket::UnixAddr; use crate::drive::{Drive, demo::DemoDriver}; use crate::event; use crate::ring::Ring; use crate::Submission; use super::{socket, socketpair}; use crate::net::TcpStream; pub struct UnixStream { inner: TcpStream, } impl UnixStream { pub fn connect(path: &impl AsRef) -> Connect { UnixStream::connect_on_driver(path, DemoDriver::default()) } pub fn pair() -> io::Result<(UnixStream, UnixStream)> { UnixStream::pair_on_driver(DemoDriver::default()) } } impl UnixStream { pub fn connect_on_driver(path: &impl AsRef, driver: D) -> Connect { let fd = match socket() { Ok(fd) => fd, Err(e) => return Connect(Err(Some(e))), }; let addr = Box::new(SockAddr::Unix(UnixAddr::new(path.as_ref()).unwrap())); Connect(Ok(driver.submit(event::Connect { fd, addr }))) } pub fn pair_on_driver(driver: D) -> io::Result<(UnixStream, UnixStream)> { let (fd1, fd2) = socketpair()?; let ring1 = Ring::new(driver.clone()); let ring2 = Ring::new(driver); Ok((UnixStream::from_fd(fd1, ring1), UnixStream::from_fd(fd2, ring2))) } } impl UnixStream { pub(super) fn from_fd(fd: RawFd, ring: Ring) -> UnixStream { UnixStream { inner: TcpStream::from_fd(fd, ring), } } #[inline(always)] fn inner(self: Pin<&mut Self>) -> Pin<&mut TcpStream> { unsafe { Pin::map_unchecked_mut(self, |this| &mut this.inner) } } } pub struct Connect( Result, Option> ); impl Future for Connect { type Output = io::Result>; fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll { unsafe { match &mut Pin::get_unchecked_mut(self).0 { Ok(submission) => { let mut submission = Pin::new_unchecked(submission); let (connect, result) = ready!(submission.as_mut().poll(ctx)); result?; let driver = submission.driver().clone(); Poll::Ready(Ok(UnixStream::from_fd(connect.fd, Ring::new(driver)))) } Err(err) => { let err = err.take().expect("polled Connect future after completion"); Poll::Ready(Err(err)) } } } } } impl AsyncRead for UnixStream { fn poll_read(self: Pin<&mut Self>, ctx: &mut Context<'_>, buf: &mut [u8]) -> Poll> { self.inner().poll_read(ctx, buf) } } impl AsyncBufRead for UnixStream { fn poll_fill_buf(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { self.inner().poll_fill_buf(ctx) } fn consume(self: Pin<&mut Self>, amt: usize) { self.inner().consume(amt) } } impl AsyncWrite for UnixStream { fn poll_write(self: Pin<&mut Self>, ctx: &mut Context<'_>, slice: &[u8]) -> Poll> { self.inner().poll_write(ctx, slice) } fn poll_flush(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { self.inner().poll_flush(ctx) } fn poll_close(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { self.inner().poll_close(ctx) } } ================================================ FILE: tests/basic-read.rs ================================================ use std::fs::File; use std::os::unix::io::AsRawFd; use ringbahn::Submission; use ringbahn::event::Read; use ringbahn::drive::demo; const ASSERT: &[u8] = b"But this formidable power of death -"; #[test] fn read_file() { let file = File::open("props.txt").unwrap(); let read = Read { fd: file.as_raw_fd(), buf: vec![0; 4096].into(), offset: 0, }; let (read, result) = futures::executor::block_on(Submission::new(read, demo::driver())); assert!(result.is_ok()); assert_eq!(&read.buf[0..ASSERT.len()], ASSERT); } ================================================ FILE: tests/basic-readv.rs ================================================ use std::fs::File; use std::os::unix::io::AsRawFd; use ringbahn::Submission; use ringbahn::event::ReadVectored; use ringbahn::drive::demo; const ASSERT: &[u8] = b"But this formidable power of death -"; #[test] fn readv_file() { let file = File::open("props.txt").unwrap(); let vec1: Box<[u8]> = Box::new([0; 4]); let vec2: Box<[u8]> = Box::new([0; 5]); let vec3: Box<[u8]> = Box::new([0; 10]); let readv = ReadVectored { fd: file.as_raw_fd(), bufs: vec![vec1, vec2, vec3].into_boxed_slice(), offset: 0, }; let (readv, result) = futures::executor::block_on(Submission::new(readv, demo::driver())); assert!(result.is_ok()); assert_eq!(readv.bufs[0][..], ASSERT[0..4]); assert_eq!(readv.bufs[1][..], ASSERT[4..9]); assert_eq!(readv.bufs[2][..], ASSERT[9..19]); } ================================================ FILE: tests/basic-write.rs ================================================ use std::io::Read; use std::os::unix::io::AsRawFd; use ringbahn::Submission; use ringbahn::event::Write; use ringbahn::drive::demo; const ASSERT: &[u8] = b"But this formidable power of death -"; #[test] fn write_file() { let mut file = tempfile::tempfile().unwrap(); let write = Write { fd: file.as_raw_fd(), buf: Box::from(ASSERT), offset: 0, }; let (_, result) = futures::executor::block_on(Submission::new(write, demo::driver())); assert_eq!(result.unwrap() as usize, ASSERT.len()); let mut buf = vec![]; assert_eq!(file.read_to_end(&mut buf).unwrap(), ASSERT.len()); assert_eq!(&buf[0..ASSERT.len()], ASSERT); } ================================================ FILE: tests/basic-writev.rs ================================================ use std::io::Read; use std::os::unix::io::AsRawFd; use ringbahn::Submission; use ringbahn::event::WriteVectored; use ringbahn::drive::demo; const ASSERT: &[u8] = b"But this formidable power of death -"; #[test] fn writev_file() { let mut file = tempfile::tempfile().unwrap(); let vec1 = &ASSERT[0..4]; let vec2 = &ASSERT[4..9]; let vec3 = &ASSERT[9..]; let writev = WriteVectored { fd: file.as_raw_fd(), bufs: vec![vec1.into(), vec2.into(), vec3.into()].into(), offset: 0, }; let (_, result) = futures::executor::block_on(Submission::new(writev, demo::driver())); assert_eq!(result.unwrap() as usize, ASSERT.len()); let mut buf = vec![]; assert_eq!(file.read_to_end(&mut buf).unwrap(), ASSERT.len()); assert_eq!(&buf[0..ASSERT.len()], ASSERT); } ================================================ FILE: tests/file-close.rs ================================================ use futures::{AsyncReadExt, AsyncWriteExt}; use ringbahn::fs::File; const ASSERT: &[u8] = b"But this formidable power of death -"; #[test] fn read_and_close_file() { futures::executor::block_on(async move { let mut file = File::open("props.txt").await.unwrap(); let mut buf = vec![0; 4096]; assert!(file.read(&mut buf).await.is_ok()); assert_eq!(&buf[0..ASSERT.len()], ASSERT); file.close().await.unwrap(); }); } ================================================ FILE: tests/file-read.rs ================================================ use futures::AsyncReadExt; use ringbahn::fs::File; const ASSERT: &[u8] = b"But this formidable power of death -"; #[test] fn read_file() { futures::executor::block_on(async move { let mut file = File::open("props.txt").await.unwrap(); let mut buf = vec![0; 4096]; assert!(file.read(&mut buf).await.is_ok()); assert_eq!(&buf[0..ASSERT.len()], ASSERT); }); } #[test] fn read_to_end() { futures::executor::block_on(async move { let mut file = File::open("props.txt").await.unwrap(); let mut buf = Vec::new(); assert!(file.read_to_end(&mut buf).await.is_ok()); assert_eq!(&buf[0..ASSERT.len()], ASSERT); }); } ================================================ FILE: tests/file-seek.rs ================================================ use std::io::SeekFrom; use futures::{AsyncSeekExt, AsyncReadExt, AsyncWriteExt}; use ringbahn::fs::File; #[test] fn seek_to_end() { futures::executor::block_on(async move { let mut file = File::open("props.txt").await.unwrap(); assert_eq!(file.seek(SeekFrom::End(0)).await.unwrap(), 792); }); } #[test] fn seek_and_then_io() { futures::executor::block_on(async move { let mut file: File = tempfile::tempfile().unwrap().into(); assert_eq!(file.seek(SeekFrom::End(0)).await.unwrap(), 0); file.write(b"abcdef").await.unwrap(); let mut buf = [0; 16]; assert_eq!(file.seek(SeekFrom::Start(0)).await.unwrap(), 0); file.read(&mut buf).await.unwrap(); assert_eq!(&buf[0..6], b"abcdef"); }); } ================================================ FILE: tests/file-write.rs ================================================ use std::io::SeekFrom; use futures::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt}; use ringbahn::fs::File; const ASSERT: &[u8] = b"But this formidable power of death -"; #[test] fn write_file() { let file = tempfile::tempfile().unwrap(); let mut file = File::from(file); futures::executor::block_on(async move { assert_eq!(file.write(ASSERT).await.unwrap(), ASSERT.len()); let mut buf = vec![]; assert!(file.seek(SeekFrom::Start(0)).await.is_ok()); assert_eq!(file.read_to_end(&mut buf).await.unwrap(), ASSERT.len()); assert_eq!(&buf[0..ASSERT.len()], ASSERT); }); } #[test] fn select_complete_many_futures() { async fn act() { let file = tempfile::tempfile().unwrap(); let mut file = File::from(file); file.write_all(b"hello, world!").await.unwrap(); } futures::executor::block_on(async move { use futures::FutureExt; let mut f1 = Box::pin(act().fuse()); let mut f2 = Box::pin(act().fuse()); let mut f3 = Box::pin(act().fuse()); let mut f4 = Box::pin(act().fuse()); let mut f5 = Box::pin(act().fuse()); let mut f6 = Box::pin(act().fuse()); let mut f7 = Box::pin(act().fuse()); let mut f8 = Box::pin(act().fuse()); loop { futures::select! { _ = f1 => (), _ = f2 => (), _ = f3 => (), _ = f4 => (), _ = f5 => (), _ = f6 => (), _ = f7 => (), _ = f8 => (), complete => break, } } }); } ================================================ FILE: tests/println.rs ================================================ use ringbahn::drive::demo; #[test] fn println() { futures::executor::block_on(async { ringbahn::println!(demo::driver(), "Hello, world!").await }) } #[test] fn print() { futures::executor::block_on(async { ringbahn::print!(demo::driver(), "Hello, world!").await }) } #[test] fn eprintln() { futures::executor::block_on(async { ringbahn::eprintln!(demo::driver(), "Hello, world!").await }) } #[test] fn eprint() { futures::executor::block_on(async { ringbahn::eprint!(demo::driver(), "Hello, world!").await }) } ================================================ FILE: tests/registered-fd.rs ================================================ use std::os::unix::io::IntoRawFd; use ringbahn::event::*; use ringbahn::drive::{demo, Drive}; use iou::sqe::*; #[test] fn test_registered_fd_ops() { // open and register file let file = std::fs::File::open("props.txt").unwrap(); let fd = demo::registrar().unwrap() .register_files(&[file.into_raw_fd()]).unwrap().next().unwrap(); futures::executor::block_on(async move { // read file and print contents to stdout let buf = vec![0; 1024].into_boxed_slice(); let (event, result) = demo::driver().submit(Read { fd, buf, offset: 0 }).await; let n = result.unwrap() as _; let data = String::from_utf8_lossy(&event.buf[..n]).to_owned(); ringbahn::println!(demo::driver(), "{}", data).await; // statx file and print statx to stdout let (event, result) = demo::driver().submit(Statx::without_path( fd, StatxFlags::empty(), StatxMode::all(), )).await; result.unwrap(); ringbahn::println!(demo::driver(), "{:?}", event.statx).await; // close file with ringbahn let (_, result) = demo::driver().submit(Close { fd }).await; result.unwrap(); }); } ================================================ FILE: tests/stdio.rs ================================================ use futures::AsyncWriteExt; use ringbahn::{drive::demo}; const ASSERT: &[u8] = b"Hello, world!\n"; #[test] fn write_stdout() { futures::executor::block_on(async { let n = ringbahn::io::stdout().write(ASSERT).await.unwrap(); assert_eq!(n, ASSERT.len()); let mut stdout = ringbahn::io::stdout_on_driver(demo::driver()); let n = stdout.write(ASSERT).await.unwrap(); assert_eq!(n, ASSERT.len()); }); }