-
Notifications
You must be signed in to change notification settings - Fork 117
Description
This week's discussions have convinced me to take another look at how libtock-rs
could integrate with the futures ecosystem. Unfortunately, I'm having a hard time seeing how that could work. I decided to open this issue to gather ideas.
For example: what should the async equivalent of this API look like?
/// Reads data from the UART into `buffer`. Blocks
/// until the buffer is completely full, then returns.
fn read(buffer: &mut [u8]) { ... }
One idea is to return a type that implements Future
which completes when the read is finished:
/// Fills `buffer` with data from the UART. The returned
/// future completes when `buffer` has been filled.
fn read(buffer: &mut [u8]) -> impl Future<Output = ()> { ... }
However, I don't see a sound way to implement that API. If read()
calls ReadWriteAllow, then the following invocation causes a use-after-free:
{
let mut buffer = [0; 8];
// Shares buffer with the kernel
let future = read(&mut buffer);
// Prevents the un-allow from happening
core::mem::forget(future);
}
// The kernel has a dangling ReadWriteAllow here.
We could delay sharing buffer
with the kernel until the returned Future
is polled, but this is unsound as well:
{
let waker = /* Waker creation omitted because it's verbose */;
let mut buffer = [0; 8];
let mut future = Box::pin(read(&mut buffer));
// Shares buffer with the kernel.
let _ = future.as_mut().poll(&mut Context::from_waker(&waker));
// Prevents the un-allow from happening.
core::mem::forget(future);
}
// The kernel has a dangling ReadWriteAllow here.
I can think of other designs that are probably sound, but impractical and/or unergonomic:
// Note the 'static -- this only works with global buffers! IIRC that's
// unacceptable for Ti50, and very expensive in general.
fn read(buffer: &'static mut [u8]) -> impl Future<Output = &'static mut [u8]> { ... }
// Pass ownership of the buffer, relies on dynamic memory allocation. Probably
// more practical than the above but still expensive.
fn read(buffer: Box<[u8]>) -> impl Future<Output = Box<[u8]>> { ... }
Any other ideas on how this could work?