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:
AsyncReadoffutures-io, if the handle implementsRead.AsyncWriteoffutures-io, if it implementsWrite.AsyncSeekoffutures-io, if it implementsSeek.Streamoffutures-core, if it is anIteratorwhose items areSend + 'static.
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>
impl<T> Unblock<T>
Sourcepub fn new(io: T) -> Self
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.
Sourcepub fn with_capacity(cap: NonZeroUsize, io: T) -> Self
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.
Sourcepub async fn get_mut(&mut self) -> &mut T
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();Sourcepub async fn with_mut<R, F>(&mut self, op: F) -> R
pub async fn with_mut<R, F>(&mut self, op: F) -> R
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);Sourcepub async fn into_inner(self) -> T
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>
impl<T> AsyncRead for Unblock<T>
Source§impl<T> AsyncWrite for Unblock<T>
impl<T> AsyncWrite for Unblock<T>
Source§fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<Result<usize>>
fn poll_write( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], ) -> Poll<Result<usize>>
buf into the object. Read more