Skip to content

Commit be8a5cd

Browse files
authored
support instance reuse for WASIp3 HTTP components (#3328)
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. add support for configuring instance reuse via CLI options update to Wasmtime 39.0.1 Signed-off-by: Joel Dice <joel.dice@fermyon.com>
1 parent 7fa967b commit be8a5cd

File tree

21 files changed

+1273
-816
lines changed

21 files changed

+1273
-816
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ spin-trigger = { path = "crates/trigger" }
7474
spin-trigger-http = { path = "crates/trigger-http" }
7575
spin-trigger-redis = { path = "crates/trigger-redis" }
7676
terminal = { path = "crates/terminal" }
77+
rand.workspace = true
7778

7879
[target.'cfg(target_os = "linux")'.dependencies]
7980
# This needs to be an explicit dependency to enable
@@ -179,9 +180,9 @@ wasm-metadata = "0.240.0"
179180
wasm-pkg-client = "0.11"
180181
wasm-pkg-common = "0.11"
181182
wasmparser = "0.240.0"
182-
wasmtime = { version = "38.0.4", features = ["component-model-async"] }
183-
wasmtime-wasi = { version = "38.0.4", features = ["p3"] }
184-
wasmtime-wasi-http = { version = "38.0.4", features = ["p3"] }
183+
wasmtime = { version = "39.0.1", features = ["component-model-async"] }
184+
wasmtime-wasi = { version = "39.0.1", features = ["p3"] }
185+
wasmtime-wasi-http = { version = "39.0.1", features = ["p3", "component-model-async"] }
185186
wit-component = "0.240.0"
186187
wit-parser = "0.240.0"
187188

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
}

crates/factor-wasi/src/wasi_2023_10_18.rs

Lines changed: 63 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -21,72 +21,72 @@ mod bindings {
2121
path: "../../wit",
2222
world: "wasi:cli/reactor@0.2.0-rc-2023-10-18",
2323
imports: {
24-
"wasi:io/streams/[drop]output-stream": async | trappable,
25-
"wasi:io/streams/[drop]input-stream": async | trappable,
26-
"wasi:filesystem/types/[method]descriptor.access-at": async | trappable,
27-
"wasi:filesystem/types/[method]descriptor.advise": async | trappable,
28-
"wasi:filesystem/types/[method]descriptor.change-directory-permissions-at": async | trappable,
29-
"wasi:filesystem/types/[method]descriptor.change-file-permissions-at": async | trappable,
30-
"wasi:filesystem/types/[method]descriptor.create-directory-at": async | trappable,
31-
"wasi:filesystem/types/[method]descriptor.get-flags": async | trappable,
32-
"wasi:filesystem/types/[method]descriptor.get-type": async | trappable,
33-
"wasi:filesystem/types/[method]descriptor.is-same-object": async | trappable,
34-
"wasi:filesystem/types/[method]descriptor.link-at": async | trappable,
35-
"wasi:filesystem/types/[method]descriptor.lock-exclusive": async | trappable,
36-
"wasi:filesystem/types/[method]descriptor.lock-shared": async | trappable,
37-
"wasi:filesystem/types/[method]descriptor.metadata-hash": async | trappable,
38-
"wasi:filesystem/types/[method]descriptor.metadata-hash-at": async | trappable,
39-
"wasi:filesystem/types/[method]descriptor.open-at": async | trappable,
40-
"wasi:filesystem/types/[method]descriptor.read": async | trappable,
41-
"wasi:filesystem/types/[method]descriptor.read-directory": async | trappable,
42-
"wasi:filesystem/types/[method]descriptor.readlink-at": async | trappable,
43-
"wasi:filesystem/types/[method]descriptor.remove-directory-at": async | trappable,
44-
"wasi:filesystem/types/[method]descriptor.rename-at": async | trappable,
45-
"wasi:filesystem/types/[method]descriptor.set-size": async | trappable,
46-
"wasi:filesystem/types/[method]descriptor.set-times": async | trappable,
47-
"wasi:filesystem/types/[method]descriptor.set-times-at": async | trappable,
48-
"wasi:filesystem/types/[method]descriptor.stat": async | trappable,
49-
"wasi:filesystem/types/[method]descriptor.stat-at": async | trappable,
50-
"wasi:filesystem/types/[method]descriptor.symlink-at": async | trappable,
51-
"wasi:filesystem/types/[method]descriptor.sync": async | trappable,
52-
"wasi:filesystem/types/[method]descriptor.sync-data": async | trappable,
53-
"wasi:filesystem/types/[method]descriptor.try-lock-exclusive": async | trappable,
54-
"wasi:filesystem/types/[method]descriptor.try-lock-shared": async | trappable,
55-
"wasi:filesystem/types/[method]descriptor.unlink-file-at": async | trappable,
56-
"wasi:filesystem/types/[method]descriptor.unlock": async | trappable,
57-
"wasi:filesystem/types/[method]descriptor.write": async | trappable,
58-
"wasi:filesystem/types/[method]directory-entry-stream.read-directory-entry": async | trappable,
59-
"wasi:io/streams/[method]input-stream.blocking-read": async | trappable,
60-
"wasi:io/streams/[method]input-stream.blocking-skip": async | trappable,
61-
"wasi:io/streams/[method]output-stream.forward": async | trappable,
62-
"wasi:io/streams/[method]output-stream.blocking-splice": async | trappable,
63-
"wasi:io/streams/[method]output-stream.blocking-flush": async | trappable,
64-
"wasi:io/streams/[method]output-stream.blocking-write-and-flush": async | trappable,
65-
"wasi:io/streams/[method]output-stream.blocking-write-zeroes-and-flush": async | trappable,
66-
"wasi:io/poll/poll-list": async | trappable,
67-
"wasi:io/poll/poll-one": async | trappable,
68-
69-
"wasi:sockets/tcp/[method]tcp-socket.start-bind": async | trappable,
70-
"wasi:sockets/tcp/[method]tcp-socket.start-connect": async | trappable,
71-
"wasi:sockets/udp/[method]udp-socket.finish-connect": async | trappable,
72-
"wasi:sockets/udp/[method]udp-socket.receive": async | trappable,
73-
"wasi:sockets/udp/[method]udp-socket.send": async | trappable,
74-
"wasi:sockets/udp/[method]udp-socket.start-bind": async | trappable,
24+
"wasi:io/streams.[drop]output-stream": async | trappable,
25+
"wasi:io/streams.[drop]input-stream": async | trappable,
26+
"wasi:filesystem/types.[method]descriptor.access-at": async | trappable,
27+
"wasi:filesystem/types.[method]descriptor.advise": async | trappable,
28+
"wasi:filesystem/types.[method]descriptor.change-directory-permissions-at": async | trappable,
29+
"wasi:filesystem/types.[method]descriptor.change-file-permissions-at": async | trappable,
30+
"wasi:filesystem/types.[method]descriptor.create-directory-at": async | trappable,
31+
"wasi:filesystem/types.[method]descriptor.get-flags": async | trappable,
32+
"wasi:filesystem/types.[method]descriptor.get-type": async | trappable,
33+
"wasi:filesystem/types.[method]descriptor.is-same-object": async | trappable,
34+
"wasi:filesystem/types.[method]descriptor.link-at": async | trappable,
35+
"wasi:filesystem/types.[method]descriptor.lock-exclusive": async | trappable,
36+
"wasi:filesystem/types.[method]descriptor.lock-shared": async | trappable,
37+
"wasi:filesystem/types.[method]descriptor.metadata-hash": async | trappable,
38+
"wasi:filesystem/types.[method]descriptor.metadata-hash-at": async | trappable,
39+
"wasi:filesystem/types.[method]descriptor.open-at": async | trappable,
40+
"wasi:filesystem/types.[method]descriptor.read": async | trappable,
41+
"wasi:filesystem/types.[method]descriptor.read-directory": async | trappable,
42+
"wasi:filesystem/types.[method]descriptor.readlink-at": async | trappable,
43+
"wasi:filesystem/types.[method]descriptor.remove-directory-at": async | trappable,
44+
"wasi:filesystem/types.[method]descriptor.rename-at": async | trappable,
45+
"wasi:filesystem/types.[method]descriptor.set-size": async | trappable,
46+
"wasi:filesystem/types.[method]descriptor.set-times": async | trappable,
47+
"wasi:filesystem/types.[method]descriptor.set-times-at": async | trappable,
48+
"wasi:filesystem/types.[method]descriptor.stat": async | trappable,
49+
"wasi:filesystem/types.[method]descriptor.stat-at": async | trappable,
50+
"wasi:filesystem/types.[method]descriptor.symlink-at": async | trappable,
51+
"wasi:filesystem/types.[method]descriptor.sync": async | trappable,
52+
"wasi:filesystem/types.[method]descriptor.sync-data": async | trappable,
53+
"wasi:filesystem/types.[method]descriptor.try-lock-exclusive": async | trappable,
54+
"wasi:filesystem/types.[method]descriptor.try-lock-shared": async | trappable,
55+
"wasi:filesystem/types.[method]descriptor.unlink-file-at": async | trappable,
56+
"wasi:filesystem/types.[method]descriptor.unlock": async | trappable,
57+
"wasi:filesystem/types.[method]descriptor.write": async | trappable,
58+
"wasi:filesystem/types.[method]directory-entry-stream.read-directory-entry": async | trappable,
59+
"wasi:io/streams.[method]input-stream.blocking-read": async | trappable,
60+
"wasi:io/streams.[method]input-stream.blocking-skip": async | trappable,
61+
"wasi:io/streams.[method]output-stream.forward": async | trappable,
62+
"wasi:io/streams.[method]output-stream.blocking-splice": async | trappable,
63+
"wasi:io/streams.[method]output-stream.blocking-flush": async | trappable,
64+
"wasi:io/streams.[method]output-stream.blocking-write-and-flush": async | trappable,
65+
"wasi:io/streams.[method]output-stream.blocking-write-zeroes-and-flush": async | trappable,
66+
"wasi:io/poll.poll-list": async | trappable,
67+
"wasi:io/poll.poll-one": async | trappable,
68+
69+
"wasi:sockets/tcp.[method]tcp-socket.start-bind": async | trappable,
70+
"wasi:sockets/tcp.[method]tcp-socket.start-connect": async | trappable,
71+
"wasi:sockets/udp.[method]udp-socket.finish-connect": async | trappable,
72+
"wasi:sockets/udp.[method]udp-socket.receive": async | trappable,
73+
"wasi:sockets/udp.[method]udp-socket.send": async | trappable,
74+
"wasi:sockets/udp.[method]udp-socket.start-bind": async | trappable,
7575
default: trappable,
7676
},
7777
with: {
78-
"wasi:io/poll/pollable": latest::io::poll::Pollable,
79-
"wasi:io/streams/input-stream": latest::io::streams::InputStream,
80-
"wasi:io/streams/output-stream": latest::io::streams::OutputStream,
81-
"wasi:io/streams/error": latest::io::streams::Error,
82-
"wasi:filesystem/types/directory-entry-stream": latest::filesystem::types::DirectoryEntryStream,
83-
"wasi:filesystem/types/descriptor": latest::filesystem::types::Descriptor,
84-
"wasi:cli/terminal-input/terminal-input": latest::cli::terminal_input::TerminalInput,
85-
"wasi:cli/terminal-output/terminal-output": latest::cli::terminal_output::TerminalOutput,
86-
"wasi:sockets/tcp/tcp-socket": latest::sockets::tcp::TcpSocket,
87-
"wasi:sockets/udp/udp-socket": UdpSocket,
88-
"wasi:sockets/network/network": latest::sockets::network::Network,
89-
"wasi:sockets/ip-name-lookup/resolve-address-stream": latest::sockets::ip_name_lookup::ResolveAddressStream,
78+
"wasi:io/poll.pollable": latest::io::poll::Pollable,
79+
"wasi:io/streams.input-stream": latest::io::streams::InputStream,
80+
"wasi:io/streams.output-stream": latest::io::streams::OutputStream,
81+
"wasi:io/streams.error": latest::io::streams::Error,
82+
"wasi:filesystem/types.directory-entry-stream": latest::filesystem::types::DirectoryEntryStream,
83+
"wasi:filesystem/types.descriptor": latest::filesystem::types::Descriptor,
84+
"wasi:cli/terminal-input.terminal-input": latest::cli::terminal_input::TerminalInput,
85+
"wasi:cli/terminal-output.terminal-output": latest::cli::terminal_output::TerminalOutput,
86+
"wasi:sockets/tcp.tcp-socket": latest::sockets::tcp::TcpSocket,
87+
"wasi:sockets/udp.udp-socket": UdpSocket,
88+
"wasi:sockets/network.network": latest::sockets::network::Network,
89+
"wasi:sockets/ip-name-lookup.resolve-address-stream": latest::sockets::ip_name_lookup::ResolveAddressStream,
9090
},
9191
});
9292
}

0 commit comments

Comments
 (0)