Skip to main content

Unblock

Struct Unblock 

Source
pub struct Unblock<T> { /* private fields */ }
Expand description

An async adapter for a blocking I/O handle.

Each operation on the handle runs as blocking work through unblock(), so the handle never blocks the thread that polls a task.

The handle can be anything that implements Read, Write, Seek or Iterator: a file, the standard input or output, a pipe to a child process, or the entries of a directory. If the handle is Send + 'static, the adapter implements:

Like unblock(), the adapter needs no runtime and works under any executor. It is Send and Sync if the handle is Send, and it is always Unpin.

§One operation at a time

The adapter runs one operation on the handle at a time. Each operation waits for the one in progress to finish, whether that is a read, a write or anything else. A read that is served from bytes already read ahead does not wait.

Two tasks can wait at once, for example the two halves of a split adapter. Both are woken when the operation ends, even if one of the wakers panics.

This is harmless for a file. It can deadlock a handle with separate read and write streams, such as a socket or a serial port. A read that waits for the peer to send data blocks a write that the peer is waiting for first.

§Reading

A read from the handle reads up to the adapter’s capacity, whatever the size of the buffer it was given. The bytes the buffer does not take are kept for the reads that follow. So the handle’s own position is ahead of what the reads have returned.

A seek accounts for this, including a seek from SeekFrom::Current. A write does not. If the handle’s reads and writes share one position, as a file’s do, seek to SeekFrom::Current(0) before writing after a read. That moves the handle’s position back to where the reads got to.

The bytes read ahead are kept across writes. A handle with two separate streams loses none of the bytes it read.

§Writing

A write takes as many of the given bytes as the capacity allows. It completes as soon as it has passed them to blocking work, which writes all of them to the handle. The next operation waits for that work to finish. If the work failed, the next write or flush returns the error.

A flush waits for the earlier writes to finish, then flushes the handle. Closing the adapter flushes it but leaves the handle open. The handle is closed when it is dropped, either with the adapter or after into_inner.

Once a write completes, its bytes no longer depend on the adapter. Dropping the adapter gives up the wait for the write in progress, not the write itself. The bytes are still written, as for unblock(), but an error goes unreported. Flush the adapter before dropping it to learn of errors.

§Iterating

The stream pulls items from the iterator in batches, in one piece of blocking work, and yields them one at a time. A batch holds as many items as the capacity, but at most 16, or fewer if the iterator ends first.

The first item of a batch is therefore delayed until the whole batch has been pulled. This matters for iterators with slow items, such as the lines of the standard input. With a capacity of 1, each item is yielded as soon as it arrives.

The stream yields None when the iterator ends. If it is polled again after that, it pulls from the iterator again.

§Using the handle directly

get_mut, with_mut and into_inner give access to the handle itself, once the operation in progress has finished. They drop the bytes read ahead and the items pulled ahead. These are lost: the handle is already past them, and the adapter cannot know what is done to the handle directly.

§Panics

If an operation on the handle panics, the poll that waits for it raises the panic again with its original payload, as for unblock(). The handle is lost with the panic, and any later use of the adapter panics too.

§Examples

A Cursor stands in for a blocking handle to read, and a Vec for one to write. The examples drive the futures with block_on from the futures crate, but the block_on of any executor works, as the adapter needs no runtime:

use std::io::Cursor;

use futures::{AsyncReadExt, AsyncWriteExt, executor::block_on};
use zruntime::Unblock;

block_on(async {
    let mut reader = Unblock::new(Cursor::new("hello, world"));
    let mut text = String::new();
    reader.read_to_string(&mut text).await?;
    assert_eq!(text, "hello, world");

    let mut writer = Unblock::new(Vec::new());
    writer.write_all(b"goodbye").await?;
    writer.flush().await?;
    assert_eq!(writer.into_inner().await, b"goodbye");

    std::io::Result::Ok(())
})
.unwrap();

Reading the standard input line by line, as the lines arrive, through a BufReader and lines from the futures crate. A Cursor stands in for std::io::stdin():

use std::io::Cursor;

use futures::{AsyncBufReadExt, StreamExt, executor::block_on, io::BufReader};
use zruntime::Unblock;

block_on(async {
    let stdin = Cursor::new("first line\nsecond line\n");
    let mut lines = BufReader::new(Unblock::new(stdin)).lines();
    while let Some(line) = lines.next().await {
        println!("{}", line?);
    }

    std::io::Result::Ok(())
})
.unwrap();

§WASI

Not available on WASI. Blocking work needs a thread of its own to run on, out of the way of the thread that polls tasks, and wasm32-wasip1 and wasm32-wasip2 cannot start a thread. Running the work on the thread that polls its future instead would block that thread, and every task on it, until the work is done, which is no async at all.

This can change once a WASI target with threads can be told apart from one without: wasm32-wasip1-threads can start threads, but no cfg sets it apart from wasm32-wasip1.

Implementations§

Source§

impl<T> Unblock<T>

Source

pub fn new(io: T) -> Self

Creates an adapter for io with the default capacity of 8 KiB.

See with_capacity for what the capacity means.

Source

pub fn with_capacity(cap: NonZeroUsize, io: T) -> Self

Creates an adapter for io with a capacity of cap.

The capacity is the most bytes that one operation reads ahead or writes. It is also the most items that one operation pulls from an iterator, with an upper limit of 16. A larger capacity means fewer and larger calls on the handle, at the cost of memory. Once the adapter has read or written, it keeps one buffer of that size for reads and one for writes.

Source

pub async fn get_mut(&mut self) -> &mut T

Borrows the handle mutably, once the operation in progress has finished.

By then, every byte a write took has been passed to the handle, though the handle may still buffer some of them itself. If the last write failed and no write or flush has reported the error yet, it is kept for the next write or flush.

The bytes read ahead and the items pulled ahead are dropped. See using the handle directly.

§Example
use std::io::Cursor;

use futures::{AsyncReadExt, executor::block_on};
use zruntime::Unblock;

block_on(async {
    let mut reader = Unblock::new(Cursor::new("hello"));
    let mut text = String::new();
    reader.read_to_string(&mut text).await?;

    assert_eq!(reader.get_mut().await.position(), 5);

    std::io::Result::Ok(())
})
.unwrap();
Source

pub async fn with_mut<R, F>(&mut self, op: F) -> R
where T: Send + 'static, F: FnOnce(&mut T) -> R + Send + 'static, R: Send + 'static,

Runs op on the handle as blocking work, once the operation in progress has finished, and returns what op returns.

Use it for what the async traits do not cover, for example reading a file’s metadata or setting its length. As with get_mut, every byte a write took has been passed to the handle before op runs. An error from the last write is kept for the next write or flush. The bytes read ahead and the items pulled ahead are dropped.

Once op has started, nothing can stop it. Dropping the future gives up the wait, but op still runs to the end. The next operation waits for it and discards what it returned. If the future is dropped before op starts, while it waits for the operation in progress, op never runs.

§Panics

If op panics, the poll of the future that would have returned its value raises the panic again. The adapter loses the handle, as for any operation on it.

§Example
use std::io::Cursor;

use futures::executor::block_on;
use zruntime::Unblock;

let mut reader = Unblock::new(Cursor::new(vec![1, 2, 3]));

let len = block_on(reader.with_mut(|cursor| cursor.get_ref().len()));

assert_eq!(len, 3);
Source

pub async fn into_inner(self) -> T

Takes the handle out of the adapter, once the operation in progress has finished.

By then, every byte a write took has been passed to the handle, though the handle may still buffer some of them itself. If the last write failed and no write or flush has reported the error yet, the error is lost, so flush the adapter first to learn of it. The bytes read ahead and the items pulled ahead are lost too. See using the handle directly.

§Example
use futures::{AsyncWriteExt, executor::block_on};
use zruntime::Unblock;

block_on(async {
    let mut writer = Unblock::new(Vec::new());
    writer.write_all(b"hello").await?;

    assert_eq!(writer.into_inner().await, b"hello");

    std::io::Result::Ok(())
})
.unwrap();

Trait Implementations§

Source§

impl<T> AsyncRead for Unblock<T>
where T: Read + Send + 'static,

Source§

fn poll_read( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll<Result<usize>>

Attempt to read from the AsyncRead into buf. Read more
Source§

fn poll_read_vectored( self: Pin<&mut Self>, cx: &mut Context<'_>, bufs: &mut [IoSliceMut<'_>], ) -> Poll<Result<usize, Error>>

Attempt to read from the AsyncRead into bufs using vectored IO operations. Read more
Source§

impl<T> AsyncSeek for Unblock<T>
where T: Seek + Send + 'static,

Source§

fn poll_seek( self: Pin<&mut Self>, cx: &mut Context<'_>, pos: SeekFrom, ) -> Poll<Result<u64>>

Attempt to seek to an offset, in bytes, in a stream. Read more
Source§

impl<T> AsyncWrite for Unblock<T>
where T: Write + Send + 'static,

Source§

fn poll_write( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], ) -> Poll<Result<usize>>

Attempt to write bytes from buf into the object. Read more
Source§

fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<()>>

Attempt to flush the object, ensuring that any buffered data reach their destination. Read more
Source§

fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<()>>

Attempt to close the object. Read more
Source§

fn poll_write_vectored( self: Pin<&mut Self>, cx: &mut Context<'_>, bufs: &[IoSlice<'_>], ) -> Poll<Result<usize, Error>>

Attempt to write bytes from bufs into the object using vectored IO operations. Read more
Source§

impl<T> Debug for Unblock<T>

Source§

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

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

impl<T> Stream for Unblock<T>
where T: Iterator + Send + 'static, T::Item: Send + 'static,

Source§

type Item = <T as Iterator>::Item

Values yielded by the stream.
Source§

fn poll_next( self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll<Option<T::Item>>

Attempt to pull out the next value of this stream, registering the current task for wakeup if the value is not yet available, and returning None if the stream is exhausted. Read more
Source§

fn size_hint(&self) -> (usize, Option<usize>)

Returns the bounds on the remaining length of the stream. Read more
Source§

impl<T> Unpin for Unblock<T>

Auto Trait Implementations§

§

impl<T> !Freeze for Unblock<T>

§

impl<T> RefUnwindSafe for Unblock<T>
where Mutex<Inner<T>>: RefUnwindSafe,

§

impl<T> Send for Unblock<T>
where Mutex<Inner<T>>: Send,

§

impl<T> Sync for Unblock<T>
where Mutex<Inner<T>>: Sync,

§

impl<T> UnsafeUnpin for Unblock<T>
where Mutex<Inner<T>>: UnsafeUnpin,

§

impl<T> UnwindSafe for Unblock<T>
where Mutex<Inner<T>>: UnwindSafe,

Blanket Implementations§

Source§

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

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

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

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

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

Source§

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

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

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

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

fn in_current_span(self) -> Instrumented<Self> ⓘ

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

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

Source§

fn into(self) -> U

Calls U::from(self).

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

Source§

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

Source§

type Error = !

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

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

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

Source§

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

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

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

Performs the conversion.
Source§

impl<S, T, E> TryStream for S
where S: Stream<Item = Result<T, E>> + ?Sized,

Source§

type Ok = T

The type of successful values yielded by this future
Source§

type Error = E

The type of failures yielded by this future
Source§

fn try_poll_next( self: Pin<&mut S>, cx: &mut Context<'_>, ) -> Poll<Option<Result<<S as TryStream>::Ok, <S as TryStream>::Error>>>

Poll this TryStream as if it were a Stream. Read more
Source§

impl<T> WithSubscriber for T

Source§

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

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

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

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