Skip to content
This repository was archived by the owner on Sep 8, 2025. It is now read-only.

Commit e4ffbf1

Browse files
authored
refactor concurrent_imports support in wasmtime-wit-bindgen (#6)
I had a few goals with this PR: 1. Improve the ergonomics of concurrent import bindings by supporting `async`/`await` sugar and allowing store access to be arbitrarily interspersed between `await` points -- while still preventing references to the store across `await` points. 2. Get rid of the `Data` associated types for `Host` traits and support `add_to_linker_get_host` where the `Host` impl is not the same as the `T` in `Store<T>`. 3. Allow creating, reading from, writing to, etc. streams and futures without exposing `StoreContextMut` directly. Unfortunately, after a day of intense type tetris I failed to achieve items 2 or 3, so this only covers item 1. Regarding item 1: I've introduced a new `Accessor` type which wraps a `*mut dyn VMStore` and provides access to it only via a `with` method that accepts a synchronous closure which takes a `StoreContextMut<T>` parameter. The closure can do what it likes and return an arbitrary value as long as that result has a `'static` lifetime (i.e. does not borrow from the store). This ensures that the host function is able to access the store only between `await`s and not across them; we prohibit the latter because it would prevent other async-lowered imports from running concurrently. Finally, since host function takes a `&mut Accessor<T>`, it is not possible for the reference to outlive the future returned by the host function, and since the `with` method takes `&mut self` it cannot be used recursively. Regarding items 2 and 3: In order to read from or write to streams/futures, we need to be able to efficiently lift and lower their payload types, which requires that both the payload type (of which there could be several for a given world) and the `T` in `Store<T>` be in scope. I was unable to find a way to thread those types through the various layers of closures, futures, and generated code without adding unwanted `'static` bounds and/or breaking the blanket `impl`s used for forwarding calls from `&mut X` to `X`. Also, the usual tricks of using dyn objects or vtables could only be used ergonomically to erase one of the two types but not both. I'd love to revisit this later with the help of a Rust type wizard to see if there's a strategy I missed. Signed-off-by: Joel Dice <joel.dice@fermyon.com>
1 parent 469ef15 commit e4ffbf1

10 files changed

Lines changed: 298 additions & 430 deletions

File tree

crates/misc/component-async-tests/http/src/lib.rs

Lines changed: 26 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ use {
2929
wasi::http::types::{ErrorCode, HeaderError, Method, RequestOptionsError, Scheme},
3030
wasmtime::{
3131
component::{
32-
self, ErrorContext, FutureReader, Linker, Resource, ResourceTable, StreamReader,
32+
Accessor, ErrorContext, FutureReader, Linker, Resource, ResourceTable, StreamReader,
3333
},
34-
AsContextMut, StoreContextMut,
34+
AsContextMut,
3535
},
3636
};
3737

@@ -55,18 +55,9 @@ pub trait WasiHttpView: Send + Sized {
5555
fn table(&mut self) -> &mut ResourceTable;
5656

5757
fn send_request(
58-
store: StoreContextMut<'_, Self::Data>,
58+
accessor: &mut Accessor<Self::Data>,
5959
request: Resource<Request>,
60-
) -> impl Future<
61-
Output = impl FnOnce(
62-
StoreContextMut<'_, Self::Data>,
63-
) -> wasmtime::Result<Result<Resource<Response>, ErrorCode>>
64-
+ Send
65-
+ Sync
66-
+ 'static,
67-
> + Send
68-
+ Sync
69-
+ 'static;
60+
) -> impl Future<Output = wasmtime::Result<Result<Resource<Response>, ErrorCode>>> + Send + Sync;
7061
}
7162

7263
impl<T: WasiHttpView> WasiHttpView for &mut T {
@@ -77,19 +68,11 @@ impl<T: WasiHttpView> WasiHttpView for &mut T {
7768
}
7869

7970
fn send_request(
80-
store: StoreContextMut<'_, Self::Data>,
71+
accessor: &mut Accessor<Self::Data>,
8172
request: Resource<Request>,
82-
) -> impl Future<
83-
Output = impl FnOnce(
84-
StoreContextMut<'_, Self::Data>,
85-
) -> wasmtime::Result<Result<Resource<Response>, ErrorCode>>
86-
+ Send
87-
+ Sync
88-
+ 'static,
89-
> + Send
90-
+ Sync
91-
+ 'static {
92-
T::send_request(store, request)
73+
) -> impl Future<Output = wasmtime::Result<Result<Resource<Response>, ErrorCode>>> + Send + Sync
74+
{
75+
T::send_request(accessor, request)
9376
}
9477
}
9578

@@ -103,19 +86,11 @@ impl<T: WasiHttpView> WasiHttpView for WasiHttpImpl<T> {
10386
}
10487

10588
fn send_request(
106-
store: StoreContextMut<'_, Self::Data>,
89+
accessor: &mut Accessor<Self::Data>,
10790
request: Resource<Request>,
108-
) -> impl Future<
109-
Output = impl FnOnce(
110-
StoreContextMut<'_, Self::Data>,
111-
) -> wasmtime::Result<Result<Resource<Response>, ErrorCode>>
112-
+ Send
113-
+ Sync
114-
+ 'static,
115-
> + Send
116-
+ Sync
117-
+ 'static {
118-
T::send_request(store, request)
91+
) -> impl Future<Output = wasmtime::Result<Result<Resource<Response>, ErrorCode>>> + Send + Sync
92+
{
93+
T::send_request(accessor, request)
11994
}
12095
}
12196

@@ -255,33 +230,22 @@ where
255230
Ok(Ok(stream))
256231
}
257232

258-
fn finish(
259-
mut store: StoreContextMut<'_, Self::BodyData>,
233+
async fn finish(
234+
accessor: &mut Accessor<Self::BodyData>,
260235
this: Resource<Body>,
261-
) -> impl Future<
262-
Output = impl FnOnce(
263-
StoreContextMut<'_, Self::BodyData>,
264-
)
265-
-> wasmtime::Result<Result<Option<Resource<Fields>>, ErrorCode>>
266-
+ 'static,
267-
> + Send
268-
+ Sync
269-
+ 'static {
270-
let trailers = (|| {
236+
) -> wasmtime::Result<Result<Option<Resource<Fields>>, ErrorCode>> {
237+
let trailers = accessor.with(|mut store| {
271238
let trailers = store.data_mut().table().delete(this)?.trailers;
272239
trailers
273240
.map(|v| v.read(store.as_context_mut()).map(|v| v.into_future()))
274241
.transpose()
275-
})();
276-
async move {
277-
let trailers = match trailers {
278-
Ok(Some(trailers)) => Ok(trailers.await),
279-
Ok(None) => Ok(None),
280-
Err(e) => Err(e),
281-
};
242+
})?;
282243

283-
component::for_any(move |_| Ok(Ok(trailers?)))
284-
}
244+
Ok(Ok(if let Some(trailers) = trailers {
245+
trailers.await
246+
} else {
247+
None
248+
}))
285249
}
286250

287251
fn drop(&mut self, this: Resource<Body>) -> wasmtime::Result<()> {
@@ -530,20 +494,11 @@ where
530494
impl<T: WasiHttpView> wasi::http::handler::Host for WasiHttpImpl<T> {
531495
type Data = T::Data;
532496

533-
fn handle(
534-
store: StoreContextMut<'_, Self::Data>,
497+
async fn handle(
498+
accessor: &mut Accessor<Self::Data>,
535499
request: Resource<Request>,
536-
) -> impl Future<
537-
Output = impl FnOnce(
538-
StoreContextMut<'_, Self::Data>,
539-
) -> wasmtime::Result<Result<Resource<Response>, ErrorCode>>
540-
+ Send
541-
+ Sync
542-
+ 'static,
543-
> + Send
544-
+ Sync
545-
+ 'static {
546-
Self::send_request(store, request)
500+
) -> wasmtime::Result<Result<Resource<Response>, ErrorCode>> {
501+
Self::send_request(accessor, request).await
547502
}
548503
}
549504

0 commit comments

Comments
 (0)