Skip to content

Commit

Permalink
wip exception propagation
Browse files Browse the repository at this point in the history
  • Loading branch information
harrishancock committed Jan 27, 2025
1 parent e08dc9b commit 11b6118
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 21 deletions.
23 changes: 10 additions & 13 deletions src/rust/async/future.c++
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,19 @@

namespace workerd::rust::async {

BoxFutureVoid::BoxFutureVoid(BoxFutureVoid&& other) noexcept: repr(other.repr) {
other.repr = {0, 0};
template <>
void box_future_drop_in_place(BoxFuture<kj::_::Void>* self) {
box_future_void_drop_in_place(self);
}

BoxFutureVoid::~BoxFutureVoid() noexcept {
if (repr != std::array<std::uintptr_t, 2>{0, 0}) {
box_future_void_drop_in_place(this);
}
template <>
bool box_future_poll(BoxFuture<kj::_::Void>& self, const CxxWaker& waker) {
return box_future_void_poll(self, waker);
}

bool BoxFutureVoid::poll(const CxxWaker& waker) noexcept {
return box_future_void_poll(*this, waker);
template <>
bool box_future_poll_with_co_await_waker(BoxFuture<kj::_::Void>& self, const CoAwaitWaker& waker) {
return box_future_void_poll_with_co_await_waker(self, waker);
}

bool BoxFutureVoid::poll(const CoAwaitWaker& waker) noexcept {
return box_future_void_poll_with_co_await_waker(*this, waker);
}
// ---------------------------------------------------------

} // namespace workerd::rust::async
54 changes: 49 additions & 5 deletions src/rust/async/future.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,33 @@ namespace workerd::rust::async {

class CoAwaitWaker;

template <typename T>
class BoxFuture;

// Function templates which are explicitly specialized for each instance of BoxFuture<T>.
template <typename T>
void box_future_drop_in_place(BoxFuture<T>* self);
template <typename T>
bool box_future_poll(BoxFuture<T>& self, const CxxWaker& waker);
template <typename T>
bool box_future_poll_with_co_await_waker(BoxFuture<T>& self, const CoAwaitWaker& waker);

// A `Pin<Box<dyn Future<Output = ()>>>` owned by C++.
//
// The only way to construct a BoxFutureVoid is by returning one from a Rust function.
//
// TODO(now): Figure out how to make this a template, BoxFuture<T>.
class BoxFutureVoid {
template <typename T>
class BoxFuture {
public:
BoxFutureVoid(BoxFutureVoid&&) noexcept;
~BoxFutureVoid() noexcept;
BoxFuture(BoxFuture&& other) noexcept: repr(other.repr) {
other.repr = {0, 0};
}
~BoxFuture() noexcept {
if (repr != std::array<std::uintptr_t, 2>{0, 0}) {
box_future_drop_in_place(this);
}
}

// This function constructs a `std::task::Context` in Rust wrapping the given `CxxWaker`. It then
// calls the future's `Future::poll()` trait function.
Expand All @@ -35,8 +53,12 @@ class BoxFutureVoid {
// The overload accepting a `const CxxWaker&` exists mostly to simplify testing.
//
// TODO(now): Figure out how to return non-unit/void values and exceptions.
bool poll(const CxxWaker& waker) noexcept;
bool poll(const CoAwaitWaker& waker) noexcept;
bool poll(const CxxWaker& waker) noexcept {
return box_future_poll(*this, waker);
}
bool poll(const CoAwaitWaker& waker) noexcept {
return box_future_poll_with_co_await_waker(*this, waker);
}

// Tell cxx-rs that this type follows Rust's move semantics, and can thus be passed across the FFI
// boundary.
Expand All @@ -47,8 +69,30 @@ class BoxFutureVoid {
std::array<std::uintptr_t, 2> repr;
};

// ---------------------------------------------------------

template <typename T>
class BoxFutureFallible: private BoxFuture<T> {
public:
using BoxFuture<T>::BoxFuture;
using BoxFuture<T>::poll;
using typename BoxFuture<T>::IsRelocatable;
};

// ---------------------------------------------------------

using BoxFutureVoid = BoxFuture<kj::_::Void>;

// We define this the pointer typedef so that cxx-rs can associate it with the same pointer type our
// drop function uses.
using PtrBoxFutureVoid = BoxFutureVoid*;

// ---------------------------------------------------------

using BoxFutureFallibleVoid = BoxFutureFallible<kj::_::Void>;

// We define this the pointer typedef so that cxx-rs can associate it with the same pointer type our
// drop function uses.
using PtrBoxFutureFallibleVoid = BoxFutureFallibleVoid*;

} // namespace workerd::rust::async
51 changes: 48 additions & 3 deletions src/rust/async/future.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ use cxx::ExternType;
use crate::ffi::CoAwaitWaker;
use crate::ffi::CxxWaker;

use crate::Result;

// Expose Pin<Box<dyn Future<Output = ()>> to C++ as BoxFutureVoid.
//
// We want to allow C++ to own Rust Futures in a Box. At present, cxx-rs can easily expose Box<T>
Expand All @@ -33,6 +35,11 @@ impl<T, F: Future<Output = T> + Send + 'static> From<Pin<Box<F>>> for BoxFuture<
}
}

#[repr(transparent)]
pub struct PtrBoxFuture<T>(*mut BoxFuture<T>);

// =======================================================================================

// TODO(now): Define these trait implementations with a macro?

// Safety: The size of a Pin<P> is the size of P; the size of a Box<T> is the size of a reference to
Expand All @@ -47,9 +54,6 @@ unsafe impl ExternType for BoxFuture<()> {
type Kind = cxx::kind::Trivial;
}

#[repr(transparent)]
pub struct PtrBoxFuture<T>(*mut BoxFuture<T>);

// Safety: Raw pointers are the same size in both languages.
unsafe impl ExternType for PtrBoxFuture<()> {
type Id = cxx::type_id!("workerd::rust::async::PtrBoxFutureVoid");
Expand All @@ -76,3 +80,44 @@ pub fn box_future_void_poll_with_co_await_waker(
pub unsafe fn box_future_void_drop_in_place(ptr: PtrBoxFuture<()>) {
std::ptr::drop_in_place(ptr.0);
}

// ---------------------------------------------------------

// Safety: The size of a Pin<P> is the size of P; the size of a Box<T> is the size of a reference to
// T, and references to `dyn Trait` types contain two pointers: one for the object, one for the
// vtable. So, the size of `Pin<Box<dyn Future<Output = T>>>` (a.k.a. `BoxFuture<T>`) is two
// pointers, and that is unlikely to change.
//
// https://doc.rust-lang.org/std/keyword.dyn.html
// - "As such, a dyn Trait reference contains two pointers."
unsafe impl ExternType for BoxFuture<Result<()>> {
type Id = cxx::type_id!("workerd::rust::async::BoxFutureFallibleVoid");
type Kind = cxx::kind::Trivial;
}

// Safety: Raw pointers are the same size in both languages.
unsafe impl ExternType for PtrBoxFuture<Result<()>> {
type Id = cxx::type_id!("workerd::rust::async::PtrBoxFutureFallibleVoid");
type Kind = cxx::kind::Trivial;
}

pub fn box_future_fallible_void_poll(future: &mut BoxFuture<Result<()>>, waker: &CxxWaker) -> bool {
let waker = Waker::from(waker);
let mut cx = Context::from_waker(&waker);
// TODO(now): Figure out how to propagate value-or-exception.
future.0.as_mut().poll(&mut cx).is_ready()
}

pub fn box_future_fallible_void_poll_with_co_await_waker(
future: &mut BoxFuture<Result<()>>,
waker: &CoAwaitWaker,
) -> bool {
let waker = Waker::from(waker);
let mut cx = Context::from_waker(&waker);
// TODO(now): Figure out how to propagate value-or-exception.
future.0.as_mut().poll(&mut cx).is_ready()
}

pub unsafe fn box_future_fallible_void_drop_in_place(ptr: PtrBoxFuture<Result<()>>) {
std::ptr::drop_in_place(ptr.0);
}
21 changes: 21 additions & 0 deletions src/rust/async/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ pub use await_::GuardedRustPromiseAwaiter;
use await_::PtrGuardedRustPromiseAwaiter;

mod future;
use future::box_future_fallible_void_drop_in_place;
use future::box_future_fallible_void_poll;
use future::box_future_fallible_void_poll_with_co_await_waker;
use future::box_future_void_drop_in_place;
use future::box_future_void_poll;
use future::box_future_void_poll_with_co_await_waker;
Expand All @@ -16,6 +19,7 @@ pub use promise::OwnPromiseNode;
use promise::PtrOwnPromiseNode;

mod test_futures;
use test_futures::new_errored_future_fallible_void;
use test_futures::new_layered_ready_future_void;
use test_futures::new_naive_select_future_void;
use test_futures::new_pending_future_void;
Expand All @@ -27,6 +31,8 @@ use test_futures::new_wrapped_waker_future_void;
mod waker;
use waker::RustWaker;

type Result<T> = std::io::Result<T>;

#[cxx::bridge(namespace = "workerd::rust::async")]
mod ffi {
unsafe extern "C++" {
Expand Down Expand Up @@ -65,6 +71,9 @@ mod ffi {

type BoxFutureVoid = crate::BoxFuture<()>;
type PtrBoxFutureVoid = crate::PtrBoxFuture<()>;

type BoxFutureFallibleVoid = crate::BoxFuture<crate::Result<()>>;
type PtrBoxFutureFallibleVoid = crate::PtrBoxFuture<crate::Result<()>>;
}

extern "Rust" {
Expand All @@ -74,6 +83,16 @@ mod ffi {
waker: &CoAwaitWaker,
) -> bool;
unsafe fn box_future_void_drop_in_place(ptr: PtrBoxFutureVoid);

fn box_future_fallible_void_poll(
future: &mut BoxFutureFallibleVoid,
waker: &CxxWaker,
) -> bool;
fn box_future_fallible_void_poll_with_co_await_waker(
future: &mut BoxFutureFallibleVoid,
waker: &CoAwaitWaker,
) -> bool;
unsafe fn box_future_fallible_void_drop_in_place(ptr: PtrBoxFutureFallibleVoid);
}

unsafe extern "C++" {
Expand Down Expand Up @@ -141,5 +160,7 @@ mod ffi {
fn new_layered_ready_future_void() -> BoxFutureVoid;
fn new_naive_select_future_void() -> BoxFutureVoid;
fn new_wrapped_waker_future_void() -> BoxFutureVoid;

fn new_errored_future_fallible_void() -> BoxFutureFallibleVoid;
}
}
7 changes: 7 additions & 0 deletions src/rust/async/test_futures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,3 +212,10 @@ pub fn new_wrapped_waker_future_void() -> BoxFuture<()> {
})
.into()
}

use std::io::Error;
use std::io::ErrorKind;

pub fn new_errored_future_fallible_void() -> BoxFuture<crate::Result<()>> {
Box::pin(std::future::ready(Err(Error::new(ErrorKind::Other, "test error")))).into()
}

0 comments on commit 11b6118

Please sign in to comment.