Skip to content

Commit 4d52eac

Browse files
authored
Reduce some complexity in wasi-http p3 paths (#13807)
* Use futures when creating a `FutureReader` rather than implementing `FutureProducer` directly. * Use a helper of run-with-callback to avoid manual impls of `FutureConsumer`.
1 parent dc7f6d5 commit 4d52eac

4 files changed

Lines changed: 115 additions & 137 deletions

File tree

crates/wasi-http/src/p3/body.rs

Lines changed: 33 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::FieldMap;
22
use crate::p3::bindings::http::types::{ErrorCode, Trailers};
3+
use crate::p3::helpers::FutureReaderExt;
34
use crate::p3::{WasiHttp, WasiHttpCtxView};
45
use bytes::Bytes;
56
use core::iter;
@@ -13,8 +14,8 @@ use std::sync::Arc;
1314
use tokio::sync::{mpsc, oneshot};
1415
use tokio_util::sync::PollSender;
1516
use wasmtime::component::{
16-
Access, Destination, FutureConsumer, FutureReader, Resource, Source, StreamConsumer,
17-
StreamProducer, StreamReader, StreamResult,
17+
Access, Destination, FutureReader, Resource, Source, StreamConsumer, StreamProducer,
18+
StreamReader, StreamResult,
1819
};
1920
use wasmtime::error::Context as _;
2021
use wasmtime::{AsContextMut, StoreContextMut};
@@ -39,33 +40,6 @@ pub(crate) enum Body {
3940
},
4041
}
4142

42-
/// [FutureConsumer] implementation for future passed to `consume-body`.
43-
struct BodyResultConsumer(
44-
Option<oneshot::Sender<Box<dyn Future<Output = Result<(), ErrorCode>> + Send>>>,
45-
);
46-
47-
impl<D> FutureConsumer<D> for BodyResultConsumer
48-
where
49-
D: 'static,
50-
{
51-
type Item = Result<(), ErrorCode>;
52-
53-
fn poll_consume(
54-
mut self: Pin<&mut Self>,
55-
_: &mut Context<'_>,
56-
store: StoreContextMut<D>,
57-
mut src: Source<'_, Self::Item>,
58-
_: bool,
59-
) -> Poll<wasmtime::Result<()>> {
60-
let mut res = None;
61-
src.read(store, &mut res).context("failed to read result")?;
62-
let res = res.context("result value missing")?;
63-
let tx = self.0.take().context("polled after returning `Ready`")?;
64-
_ = tx.send(Box::new(async { res }));
65-
Poll::Ready(Ok(()))
66-
}
67-
}
68-
6943
impl Body {
7044
/// Implementation of `consume-body` shared between requests and responses
7145
pub(crate) fn consume<T>(
@@ -77,25 +51,22 @@ impl Body {
7751
StreamReader<u8>,
7852
FutureReader<Result<Option<Resource<Trailers>>, ErrorCode>>,
7953
)> {
80-
Ok(match self {
54+
let (contents_rx, trailers_rx, result_tx) = match self {
8155
Body::Guest {
8256
contents_rx: Some(contents_rx),
8357
trailers_rx,
8458
result_tx,
85-
} => {
86-
fut.pipe(&mut store, BodyResultConsumer(Some(result_tx)))?;
87-
(contents_rx, trailers_rx)
88-
}
59+
} => (contents_rx, trailers_rx, result_tx),
8960
Body::Guest {
9061
contents_rx: None,
9162
trailers_rx,
9263
result_tx,
93-
} => {
94-
fut.pipe(&mut store, BodyResultConsumer(Some(result_tx)))?;
95-
(StreamReader::new(&mut store, iter::empty())?, trailers_rx)
96-
}
64+
} => (
65+
StreamReader::new(&mut store, iter::empty())?,
66+
trailers_rx,
67+
result_tx,
68+
),
9769
Body::Host { body, result_tx } => {
98-
fut.pipe(&mut store, BodyResultConsumer(Some(result_tx)))?;
9970
let (trailers_tx, trailers_rx) = oneshot::channel();
10071
(
10172
StreamReader::new(
@@ -107,9 +78,16 @@ impl Body {
10778
},
10879
)?,
10980
FutureReader::new(&mut store, trailers_rx)?,
81+
result_tx,
11082
)
11183
}
112-
})
84+
};
85+
86+
fut.pipe_cb(&mut store, |_, res| {
87+
_ = result_tx.send(Box::new(async { res }));
88+
Ok(())
89+
})?;
90+
Ok((contents_rx, trailers_rx))
11391
}
11492

11593
/// Implementation of `drop` shared between requests and responses
@@ -275,13 +253,21 @@ impl GuestBody {
275253
getter: fn(&mut T) -> WasiHttpCtxView<'_>,
276254
) -> wasmtime::Result<Self> {
277255
let (trailers_http_tx, trailers_http_rx) = oneshot::channel();
278-
trailers_rx.pipe(
279-
&mut store,
280-
GuestTrailerConsumer {
281-
tx: Some(trailers_http_tx),
282-
getter,
283-
},
284-
)?;
256+
trailers_rx.pipe_cb(&mut store, move |data, res| {
257+
let res = match res {
258+
Ok(Some(trailers)) => {
259+
let WasiHttpCtxView { table, .. } = getter(data);
260+
let trailers = table
261+
.delete(trailers)
262+
.context("failed to delete trailers")?;
263+
Ok(Some(Arc::from(trailers)))
264+
}
265+
Ok(None) => Ok(None),
266+
Err(err) => Err(err),
267+
};
268+
_ = trailers_http_tx.send(res);
269+
Ok(())
270+
})?;
285271

286272
let contents_rx = if let Some(rx) = contents_rx {
287273
let (http_tx, http_rx) = mpsc::channel(1);
@@ -401,44 +387,6 @@ impl http_body::Body for GuestBody {
401387
}
402388
}
403389

404-
/// [FutureConsumer] implementation for trailers originating in the guest.
405-
struct GuestTrailerConsumer<T> {
406-
tx: Option<oneshot::Sender<Result<Option<Arc<FieldMap>>, ErrorCode>>>,
407-
getter: fn(&mut T) -> WasiHttpCtxView<'_>,
408-
}
409-
410-
impl<D> FutureConsumer<D> for GuestTrailerConsumer<D>
411-
where
412-
D: 'static,
413-
{
414-
type Item = Result<Option<Resource<Trailers>>, ErrorCode>;
415-
416-
fn poll_consume(
417-
mut self: Pin<&mut Self>,
418-
_: &mut Context<'_>,
419-
mut store: StoreContextMut<D>,
420-
mut src: Source<'_, Self::Item>,
421-
_: bool,
422-
) -> Poll<wasmtime::Result<()>> {
423-
let mut res = None;
424-
src.read(&mut store, &mut res)
425-
.context("failed to read result")?;
426-
let res = match res.context("result value missing")? {
427-
Ok(Some(trailers)) => {
428-
let WasiHttpCtxView { table, .. } = (self.getter)(store.data_mut());
429-
let trailers = table
430-
.delete(trailers)
431-
.context("failed to delete trailers")?;
432-
Ok(Some(Arc::from(trailers)))
433-
}
434-
Ok(None) => Ok(None),
435-
Err(err) => Err(err),
436-
};
437-
_ = self.tx.take().unwrap().send(res);
438-
Poll::Ready(Ok(()))
439-
}
440-
}
441-
442390
/// [StreamProducer] implementation for bodies originating in the host.
443391
pub(crate) struct HostBodyStreamProducer<T> {
444392
pub(crate) body: UnsyncBoxBody<Bytes, ErrorCode>,

crates/wasi-http/src/p3/helpers.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
use std::pin::Pin;
2+
use std::task::{Context, Poll};
3+
use wasmtime::component::{FutureConsumer, FutureReader, Lift, Source};
4+
use wasmtime::error::Context as _;
5+
use wasmtime::{AsContextMut, StoreContextMut};
6+
7+
/// Extension methosd for `FutureReader`
8+
pub trait FutureReaderExt<T> {
9+
/// Get the underlying `FutureReader`.
10+
fn as_future_reader(self) -> FutureReader<T>;
11+
12+
/// Run `cb` with the result of this future when it's ready.
13+
///
14+
/// The `cb` is given the store's data-at-the-time, the result of the
15+
/// future, and can produce a trapping error if so desirable.
16+
fn pipe_cb<S>(
17+
self,
18+
store: S,
19+
cb: impl FnOnce(&mut S::Data, T) -> wasmtime::Result<()> + Unpin + Send + 'static,
20+
) -> wasmtime::Result<()>
21+
where
22+
Self: Sized,
23+
S: AsContextMut,
24+
T: Lift + 'static,
25+
{
26+
struct Consumer<F, D, T> {
27+
cb: Option<F>,
28+
_marker: std::marker::PhantomData<fn(D, T)>,
29+
}
30+
31+
impl<T, D, F> FutureConsumer<D> for Consumer<F, D, T>
32+
where
33+
T: Lift + 'static,
34+
F: FnOnce(&mut D, T) -> wasmtime::Result<()> + Send + Unpin + 'static,
35+
D: 'static,
36+
{
37+
type Item = T;
38+
39+
fn poll_consume(
40+
mut self: Pin<&mut Self>,
41+
_: &mut Context<'_>,
42+
mut store: StoreContextMut<D>,
43+
mut src: Source<'_, Self::Item>,
44+
_: bool,
45+
) -> Poll<wasmtime::Result<()>> {
46+
let mut res = None;
47+
src.read(&mut store, &mut res)
48+
.context("failed to read result")?;
49+
let res = res.context("result value missing")?;
50+
let cb = self.cb.take().context("polled after returning `Ready`")?;
51+
cb(store.data_mut(), res)?;
52+
Poll::Ready(Ok(()))
53+
}
54+
}
55+
56+
self.as_future_reader().pipe(
57+
store,
58+
Consumer {
59+
cb: Some(cb),
60+
_marker: std::marker::PhantomData,
61+
},
62+
)
63+
}
64+
}
65+
66+
impl<T> FutureReaderExt<T> for FutureReader<T> {
67+
fn as_future_reader(self) -> FutureReader<T> {
68+
self
69+
}
70+
}

crates/wasi-http/src/p3/host/types.rs

Lines changed: 11 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,12 @@ use crate::p3::body::{Body, HostBodyStreamProducer};
99
use crate::p3::{HeaderResult, HttpError, RequestOptionsResult, WasiHttp, WasiHttpCtxView};
1010
use core::mem;
1111
use core::pin::Pin;
12-
use core::task::{Context, Poll, ready};
1312
use http::header::CONTENT_LENGTH;
1413
use std::sync::Arc;
1514
use tokio::sync::oneshot;
16-
use wasmtime::component::{
17-
Access, FutureProducer, FutureReader, Resource, ResourceTable, StreamReader,
18-
};
15+
use wasmtime::AsContextMut;
16+
use wasmtime::component::{Access, FutureReader, Resource, ResourceTable, StreamReader};
1917
use wasmtime::error::Context as _;
20-
use wasmtime::{AsContextMut, StoreContextMut};
2118

2219
fn get_fields<'a>(
2320
table: &'a ResourceTable,
@@ -161,51 +158,13 @@ fn parse_header_value(
161158
}
162159
}
163160

164-
enum GuestBodyResultProducer {
165-
Receiver(oneshot::Receiver<Box<dyn Future<Output = Result<(), ErrorCode>> + Send>>),
166-
Future(Pin<Box<dyn Future<Output = Result<(), ErrorCode>> + Send>>),
167-
}
168-
169-
fn poll_future<T>(
170-
cx: &mut Context<'_>,
171-
fut: Pin<&mut (impl Future<Output = T> + ?Sized)>,
172-
finish: bool,
173-
) -> Poll<Option<T>> {
174-
match fut.poll(cx) {
175-
Poll::Ready(v) => Poll::Ready(Some(v)),
176-
Poll::Pending if finish => Poll::Ready(None),
177-
Poll::Pending => Poll::Pending,
178-
}
179-
}
180-
181-
impl<D> FutureProducer<D> for GuestBodyResultProducer {
182-
type Item = Result<(), ErrorCode>;
183-
184-
fn poll_produce(
185-
mut self: Pin<&mut Self>,
186-
cx: &mut Context<'_>,
187-
_: StoreContextMut<D>,
188-
finish: bool,
189-
) -> Poll<wasmtime::Result<Option<Self::Item>>> {
190-
match &mut *self {
191-
Self::Receiver(rx) => {
192-
match ready!(poll_future(cx, Pin::new(rx), finish)) {
193-
Some(Ok(fut)) => {
194-
let mut fut = Box::into_pin(fut);
195-
// poll the received future once and update state
196-
let res = poll_future(cx, fut.as_mut(), finish);
197-
*self = Self::Future(fut);
198-
res.map(Ok)
199-
}
200-
Some(Err(..)) => {
201-
// oneshot sender dropped, treat as success
202-
Poll::Ready(Ok(Some(Ok(()))))
203-
}
204-
None => Poll::Ready(Ok(None)),
205-
}
206-
}
207-
Self::Future(fut) => poll_future(cx, fut.as_mut(), finish).map(Ok),
208-
}
161+
async fn guest_body_result(
162+
rx: oneshot::Receiver<Box<dyn Future<Output = Result<(), ErrorCode>> + Send>>,
163+
) -> wasmtime::Result<Result<(), ErrorCode>> {
164+
match rx.await {
165+
Ok(fut) => Ok(Pin::from(fut).await),
166+
// oneshot sender dropped, treat as success
167+
Err(..) => Ok(Ok(())),
209168
}
210169
}
211170

@@ -375,7 +334,7 @@ impl<T> HostRequestWithStore<T> for WasiHttp {
375334
let req = table.push(req).context("failed to push request to table")?;
376335
Ok((
377336
req,
378-
FutureReader::new(&mut store, GuestBodyResultProducer::Receiver(result_rx))?,
337+
FutureReader::new(&mut store, guest_body_result(result_rx))?,
379338
))
380339
}
381340

@@ -648,7 +607,7 @@ impl<T> HostResponseWithStore<T> for WasiHttp {
648607
.context("failed to push response to table")?;
649608
Ok((
650609
res,
651-
FutureReader::new(&mut store, GuestBodyResultProducer::Receiver(result_rx))?,
610+
FutureReader::new(&mut store, guest_body_result(result_rx))?,
652611
))
653612
}
654613

crates/wasi-http/src/p3/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
pub mod bindings;
1212
mod body;
1313
mod conv;
14+
mod helpers;
1415
mod host;
1516
mod proxy;
1617
mod request;

0 commit comments

Comments
 (0)