Skip to content

Commit 9a37e13

Browse files
committed
support instance reuse for WASIp3 HTTP components
This makes use of the new `wasmtime_wasi_http::handler::ProxyHandler` utility, which provides both serial and concurrent instance reuse. We could hypothetically enable opt-in serial reuse for WASIp2 components as well using the same pattern (which is what `wasmtime serve` does), but I'll leave that for a follow-up PR, if desired. This hard-codes the configuration values (max reuse count = 128, max concurrent reuse count = 16, idle timeout = 1s) for now. Once we've decided where these values should be configured (e.g. in the spin.toml manifest, in the runtime config, or at runtime via the component itself), we can support that. See https://github.com/WebAssembly/wasi-http/issues/190 for related discussion. Signed-off-by: Joel Dice <[email protected]>
1 parent b332f52 commit 9a37e13

File tree

17 files changed

+1277
-1104
lines changed

17 files changed

+1277
-1104
lines changed

Cargo.lock

Lines changed: 390 additions & 356 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -173,16 +173,16 @@ tower-service = "0.3.3"
173173
tracing = { version = "0.1.41", features = ["log"] }
174174
url = "2.5.7"
175175
walkdir = "2"
176-
wasm-encoder = "0.239.0"
177-
wasm-metadata = "0.239.0"
176+
wasm-encoder = "0.240.0"
177+
wasm-metadata = "0.240.0"
178178
wasm-pkg-client = "0.11"
179179
wasm-pkg-common = "0.11"
180-
wasmparser = "0.239.0"
181-
wasmtime = { version = "37.0.1", features = ["component-model-async"] }
182-
wasmtime-wasi = { version = "37.0.1", features = ["p3"] }
183-
wasmtime-wasi-http = { version = "37.0.1", features = ["p3"] }
184-
wit-component = "0.239.0"
185-
wit-parser = "0.239.0"
180+
wasmparser = "0.240.0"
181+
wasmtime = { git = "https://github.com/bytecodealliance/wasmtime", branch = "release-39.0.0", features = ["component-model-async"] }
182+
wasmtime-wasi = { git = "https://github.com/bytecodealliance/wasmtime", branch = "release-39.0.0", features = ["p3"] }
183+
wasmtime-wasi-http = { git = "https://github.com/bytecodealliance/wasmtime", branch = "release-39.0.0", features = ["p3", "component-model-async"] }
184+
wit-component = "0.240.0"
185+
wit-parser = "0.240.0"
186186

187187
spin-componentize = { path = "crates/componentize" }
188188

crates/core/src/store.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ impl<T: 'static> Store<T> {
4848
pub fn data_mut(&mut self) -> &mut T {
4949
self.inner.data_mut()
5050
}
51+
52+
/// Convert `self` to the inner [`wasmtime::Store`].
53+
pub fn into_inner(self) -> wasmtime::Store<T> {
54+
self.inner
55+
}
5156
}
5257

5358
impl<T: 'static> AsRef<wasmtime::Store<T>> for Store<T> {

crates/factor-outbound-http/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ pub use wasmtime_wasi_http::{
3232
HttpResult,
3333
};
3434

35-
pub use wasi::{p2_to_p3_error_code, p3_to_p2_error_code};
35+
pub use wasi::{p2_to_p3_error_code, p3_to_p2_error_code, MutexBody};
3636

3737
#[derive(Default)]
3838
pub struct OutboundHttpFactor {

crates/factor-outbound-http/src/wasi.rs

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,17 @@ use std::{
33
future::Future,
44
io::IoSlice,
55
net::SocketAddr,
6+
ops::DerefMut,
67
pin::Pin,
7-
sync::Arc,
8+
sync::{Arc, Mutex},
89
task::{self, Context, Poll},
910
time::Duration,
1011
};
1112

1213
use bytes::Bytes;
1314
use http::{header::HOST, uri::Scheme, Uri};
1415
use http_body::{Body, Frame, SizeHint};
15-
use http_body_util::{combinators::BoxBody, BodyExt};
16+
use http_body_util::{combinators::UnsyncBoxBody, BodyExt};
1617
use hyper_util::{
1718
client::legacy::{
1819
connect::{Connected, Connection},
@@ -51,6 +52,34 @@ use crate::{
5152

5253
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(600);
5354

55+
pub struct MutexBody<T>(Mutex<T>);
56+
57+
impl<T> MutexBody<T> {
58+
pub fn new(body: T) -> Self {
59+
Self(Mutex::new(body))
60+
}
61+
}
62+
63+
impl<T: Body + Unpin> Body for MutexBody<T> {
64+
type Data = T::Data;
65+
type Error = T::Error;
66+
67+
fn poll_frame(
68+
self: Pin<&mut Self>,
69+
cx: &mut Context<'_>,
70+
) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
71+
Pin::new(self.0.lock().unwrap().deref_mut()).poll_frame(cx)
72+
}
73+
74+
fn is_end_stream(&self) -> bool {
75+
self.0.lock().unwrap().is_end_stream()
76+
}
77+
78+
fn size_hint(&self) -> SizeHint {
79+
self.0.lock().unwrap().size_hint()
80+
}
81+
}
82+
5483
pub(crate) struct HasHttp;
5584

5685
impl HasData for HasHttp {
@@ -60,14 +89,14 @@ impl HasData for HasHttp {
6089
impl p3::WasiHttpCtx for InstanceState {
6190
fn send_request(
6291
&mut self,
63-
request: http::Request<BoxBody<Bytes, p3_types::ErrorCode>>,
92+
request: http::Request<UnsyncBoxBody<Bytes, p3_types::ErrorCode>>,
6493
options: Option<p3::RequestOptions>,
6594
fut: Box<dyn Future<Output = Result<(), p3_types::ErrorCode>> + Send>,
6695
) -> Box<
6796
dyn Future<
6897
Output = Result<
6998
(
70-
http::Response<BoxBody<Bytes, p3_types::ErrorCode>>,
99+
http::Response<UnsyncBoxBody<Bytes, p3_types::ErrorCode>>,
71100
Box<dyn Future<Output = Result<(), p3_types::ErrorCode>> + Send>,
72101
),
73102
TrappableError<p3_types::ErrorCode>,
@@ -84,6 +113,8 @@ impl p3::WasiHttpCtx for InstanceState {
84113
// connection).
85114
_ = fut;
86115

116+
let request = request.map(|body| MutexBody::new(body).boxed());
117+
87118
let request_sender = RequestSender {
88119
allowed_hosts: self.allowed_hosts.clone(),
89120
component_tls_configs: self.component_tls_configs.clone(),
@@ -126,7 +157,7 @@ impl p3::WasiHttpCtx for InstanceState {
126157
sleep: None,
127158
timeout: between_bytes_timeout,
128159
}
129-
.boxed()
160+
.boxed_unsync()
130161
}),
131162
Box::new(async {
132163
// TODO: Can we plumb connection errors through to here, or

crates/factor-outbound-http/src/wasi_2023_10_18.rs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,20 @@ mod bindings {
1212
imports: { default: trappable },
1313
exports: { default: async },
1414
with: {
15-
"wasi:io/poll/pollable": latest::io::poll::Pollable,
16-
"wasi:io/streams/input-stream": latest::io::streams::InputStream,
17-
"wasi:io/streams/output-stream": latest::io::streams::OutputStream,
18-
"wasi:io/streams/error": latest::io::streams::Error,
19-
"wasi:http/types/incoming-response": latest::http::types::IncomingResponse,
20-
"wasi:http/types/incoming-request": latest::http::types::IncomingRequest,
21-
"wasi:http/types/incoming-body": latest::http::types::IncomingBody,
22-
"wasi:http/types/outgoing-response": latest::http::types::OutgoingResponse,
23-
"wasi:http/types/outgoing-request": latest::http::types::OutgoingRequest,
24-
"wasi:http/types/outgoing-body": latest::http::types::OutgoingBody,
25-
"wasi:http/types/fields": latest::http::types::Fields,
26-
"wasi:http/types/response-outparam": latest::http::types::ResponseOutparam,
27-
"wasi:http/types/future-incoming-response": latest::http::types::FutureIncomingResponse,
28-
"wasi:http/types/future-trailers": latest::http::types::FutureTrailers,
15+
"wasi:io/poll.pollable": latest::io::poll::Pollable,
16+
"wasi:io/streams.input-stream": latest::io::streams::InputStream,
17+
"wasi:io/streams.output-stream": latest::io::streams::OutputStream,
18+
"wasi:io/streams.error": latest::io::streams::Error,
19+
"wasi:http/types.incoming-response": latest::http::types::IncomingResponse,
20+
"wasi:http/types.incoming-request": latest::http::types::IncomingRequest,
21+
"wasi:http/types.incoming-body": latest::http::types::IncomingBody,
22+
"wasi:http/types.outgoing-response": latest::http::types::OutgoingResponse,
23+
"wasi:http/types.outgoing-request": latest::http::types::OutgoingRequest,
24+
"wasi:http/types.outgoing-body": latest::http::types::OutgoingBody,
25+
"wasi:http/types.fields": latest::http::types::Fields,
26+
"wasi:http/types.response-outparam": latest::http::types::ResponseOutparam,
27+
"wasi:http/types.future-incoming-response": latest::http::types::FutureIncomingResponse,
28+
"wasi:http/types.future-trailers": latest::http::types::FutureTrailers,
2929
},
3030
});
3131
}

crates/factor-outbound-http/src/wasi_2023_11_10.rs

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,21 @@ mod bindings {
1515
imports: { default: trappable },
1616
exports: { default: async },
1717
with: {
18-
"wasi:io/poll/pollable": latest::io::poll::Pollable,
19-
"wasi:io/streams/input-stream": latest::io::streams::InputStream,
20-
"wasi:io/streams/output-stream": latest::io::streams::OutputStream,
21-
"wasi:io/error/error": latest::io::error::Error,
22-
"wasi:http/types/incoming-response": latest::http::types::IncomingResponse,
23-
"wasi:http/types/incoming-request": latest::http::types::IncomingRequest,
24-
"wasi:http/types/incoming-body": latest::http::types::IncomingBody,
25-
"wasi:http/types/outgoing-response": latest::http::types::OutgoingResponse,
26-
"wasi:http/types/outgoing-request": latest::http::types::OutgoingRequest,
27-
"wasi:http/types/outgoing-body": latest::http::types::OutgoingBody,
28-
"wasi:http/types/fields": latest::http::types::Fields,
29-
"wasi:http/types/response-outparam": latest::http::types::ResponseOutparam,
30-
"wasi:http/types/future-incoming-response": latest::http::types::FutureIncomingResponse,
31-
"wasi:http/types/future-trailers": latest::http::types::FutureTrailers,
32-
"wasi:http/types/request-options": latest::http::types::RequestOptions,
18+
"wasi:io/poll.pollable": latest::io::poll::Pollable,
19+
"wasi:io/streams.input-stream": latest::io::streams::InputStream,
20+
"wasi:io/streams.output-stream": latest::io::streams::OutputStream,
21+
"wasi:io/error.error": latest::io::error::Error,
22+
"wasi:http/types.incoming-response": latest::http::types::IncomingResponse,
23+
"wasi:http/types.incoming-request": latest::http::types::IncomingRequest,
24+
"wasi:http/types.incoming-body": latest::http::types::IncomingBody,
25+
"wasi:http/types.outgoing-response": latest::http::types::OutgoingResponse,
26+
"wasi:http/types.outgoing-request": latest::http::types::OutgoingRequest,
27+
"wasi:http/types.outgoing-body": latest::http::types::OutgoingBody,
28+
"wasi:http/types.fields": latest::http::types::Fields,
29+
"wasi:http/types.response-outparam": latest::http::types::ResponseOutparam,
30+
"wasi:http/types.future-incoming-response": latest::http::types::FutureIncomingResponse,
31+
"wasi:http/types.future-trailers": latest::http::types::FutureTrailers,
32+
"wasi:http/types.request-options": latest::http::types::RequestOptions,
3333
},
3434
});
3535
}

0 commit comments

Comments
 (0)