Skip to content

Commit 156dacd

Browse files
authored
Merge pull request #3063 from alexcrichton/optimize-per-request-dispatch
Optimize per-request work dispatching to wasm
2 parents f6bb6c9 + 52b2160 commit 156dacd

File tree

10 files changed

+167
-131
lines changed

10 files changed

+167
-131
lines changed

Cargo.lock

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

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ pub mod exports {
4343
}
4444
}
4545

46+
pub use bindings::{Proxy, ProxyIndices};
4647
use wasi::http::types::{
4748
Error as HttpError, Fields, FutureIncomingResponse, FutureTrailers, Headers, IncomingBody,
4849
IncomingRequest, IncomingResponse, Method, OutgoingBody, OutgoingRequest, OutgoingResponse,

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ pub mod exports {
4747
}
4848
}
4949

50+
pub use bindings::{Proxy, ProxyIndices};
5051
use wasi::http::types::{
5152
DnsErrorPayload, ErrorCode as HttpErrorCode, FieldSizePayload, Fields, FutureIncomingResponse,
5253
FutureTrailers, HeaderError, Headers, IncomingBody, IncomingRequest, IncomingResponse, Method,

crates/http/Cargo.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@ serde = { workspace = true }
1616
spin-app = { path = "../app", optional = true }
1717
tracing = { workspace = true }
1818
wasmtime = { workspace = true }
19-
wasmtime-wasi-http = { workspace = true, optional = true }
19+
wasmtime-wasi = { workspace = true }
20+
wasmtime-wasi-http = { workspace = true }
21+
spin-factor-outbound-http = { path = "../factor-outbound-http" }
2022

2123
[dev-dependencies]
2224
toml = { workspace = true }
2325

2426
[features]
2527
default = ["runtime"]
26-
runtime = ["dep:spin-app", "dep:wasmtime-wasi-http"]
28+
runtime = ["dep:spin-app"]

crates/http/src/trigger.rs

Lines changed: 42 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
use serde::{Deserialize, Serialize};
2+
use spin_factor_outbound_http::wasi_2023_10_18::ProxyIndices as ProxyIndices2023_10_18;
3+
use spin_factor_outbound_http::wasi_2023_11_10::ProxyIndices as ProxyIndices2023_11_10;
24
use wasmtime::component::Component;
5+
use wasmtime_wasi::bindings::CommandIndices;
6+
use wasmtime_wasi_http::bindings::ProxyIndices;
37

48
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
59
#[serde(deny_unknown_fields)]
@@ -14,64 +18,57 @@ pub fn default_base() -> String {
1418
}
1519

1620
/// The type of http handler export used by a component.
17-
#[derive(Clone, Copy)]
1821
pub enum HandlerType {
1922
Spin,
20-
Wagi,
21-
Wasi0_2,
22-
Wasi2023_11_10,
23-
Wasi2023_10_18,
23+
Wagi(CommandIndices),
24+
Wasi0_2(ProxyIndices),
25+
Wasi2023_11_10(ProxyIndices2023_11_10),
26+
Wasi2023_10_18(ProxyIndices2023_10_18),
2427
}
2528

2629
/// The `incoming-handler` export for `wasi:http` version rc-2023-10-18
27-
pub const WASI_HTTP_EXPORT_2023_10_18: &str = "wasi:http/incoming-handler@0.2.0-rc-2023-10-18";
30+
const WASI_HTTP_EXPORT_2023_10_18: &str = "wasi:http/incoming-handler@0.2.0-rc-2023-10-18";
2831
/// The `incoming-handler` export for `wasi:http` version rc-2023-11-10
29-
pub const WASI_HTTP_EXPORT_2023_11_10: &str = "wasi:http/incoming-handler@0.2.0-rc-2023-11-10";
32+
const WASI_HTTP_EXPORT_2023_11_10: &str = "wasi:http/incoming-handler@0.2.0-rc-2023-11-10";
3033
/// The `incoming-handler` export prefix for all `wasi:http` 0.2 versions
31-
pub const WASI_HTTP_EXPORT_0_2_PREFIX: &str = "wasi:http/incoming-handler@0.2";
34+
const WASI_HTTP_EXPORT_0_2_PREFIX: &str = "wasi:http/incoming-handler@0.2";
3235
/// The `inbound-http` export for `fermyon:spin`
33-
pub const SPIN_HTTP_EXPORT: &str = "fermyon:spin/inbound-http";
36+
const SPIN_HTTP_EXPORT: &str = "fermyon:spin/inbound-http";
3437

3538
impl HandlerType {
3639
/// Determine the handler type from the exports of a component.
37-
pub fn from_component(
38-
engine: &wasmtime::Engine,
39-
component: &Component,
40-
) -> anyhow::Result<HandlerType> {
41-
let mut handler_ty = None;
40+
pub fn from_component(component: &Component) -> anyhow::Result<HandlerType> {
41+
let mut candidates = Vec::new();
42+
if let Ok(indices) = ProxyIndices::new(component) {
43+
candidates.push(HandlerType::Wasi0_2(indices));
44+
}
45+
if let Ok(indices) = ProxyIndices2023_10_18::new(component) {
46+
candidates.push(HandlerType::Wasi2023_10_18(indices));
47+
}
48+
if let Ok(indices) = ProxyIndices2023_11_10::new(component) {
49+
candidates.push(HandlerType::Wasi2023_11_10(indices));
50+
}
51+
if component.export_index(None, SPIN_HTTP_EXPORT).is_some() {
52+
candidates.push(HandlerType::Spin);
53+
}
4254

43-
let mut set = |ty: HandlerType| {
44-
if handler_ty.is_none() {
45-
handler_ty = Some(ty);
46-
Ok(())
47-
} else {
48-
Err(anyhow::anyhow!(
49-
"component exports multiple different handlers but \
50-
it's expected to export only one"
51-
))
52-
}
53-
};
54-
let ty = component.component_type();
55-
for (name, _) in ty.exports(engine) {
56-
match name {
57-
WASI_HTTP_EXPORT_2023_10_18 => set(HandlerType::Wasi2023_10_18)?,
58-
WASI_HTTP_EXPORT_2023_11_10 => set(HandlerType::Wasi2023_11_10)?,
59-
SPIN_HTTP_EXPORT => set(HandlerType::Spin)?,
60-
name if name.starts_with(WASI_HTTP_EXPORT_0_2_PREFIX) => set(HandlerType::Wasi0_2)?,
61-
_ => {}
55+
match candidates.len() {
56+
0 => {
57+
anyhow::bail!(
58+
"Expected component to export one of \
59+
`{WASI_HTTP_EXPORT_2023_10_18}`, \
60+
`{WASI_HTTP_EXPORT_2023_11_10}`, \
61+
`{WASI_HTTP_EXPORT_0_2_PREFIX}.*`, \
62+
or `{SPIN_HTTP_EXPORT}` but it exported none of those. \
63+
This may mean the component handles a different trigger, or that its `wasi:http` export is newer then those supported by Spin. \
64+
If you're sure this is an HTTP module, check if a Spin upgrade is available: this may handle the newer version."
65+
)
6266
}
67+
1 => Ok(candidates.pop().unwrap()),
68+
_ => anyhow::bail!(
69+
"component exports multiple different handlers but \
70+
it's expected to export only one"
71+
),
6372
}
64-
65-
handler_ty.ok_or_else(|| {
66-
anyhow::anyhow!(
67-
"Expected component to export one of \
68-
`{WASI_HTTP_EXPORT_2023_10_18}`, \
69-
`{WASI_HTTP_EXPORT_2023_11_10}`, \
70-
`{WASI_HTTP_EXPORT_0_2_PREFIX}.*`, \
71-
or `{SPIN_HTTP_EXPORT}` but it exported none of those. \
72-
This may mean the component handles a different trigger, or that its `wasi:http` export is newer then those supported by Spin. \
73-
If you're sure this is an HTTP module, check if a Spin upgrade is available: this may handle the newer version."
74-
)
75-
})
7673
}
7774
}

crates/trigger-http/src/server.rs

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ use tokio::{
2828
task,
2929
};
3030
use tracing::Instrument;
31+
use wasmtime_wasi::bindings::CommandIndices;
3132
use wasmtime_wasi_http::body::HyperOutgoingBody;
3233

3334
use crate::{
@@ -107,17 +108,18 @@ impl<F: RuntimeFactors> HttpServer<F> {
107108
let component_handler_types = component_trigger_configs
108109
.iter()
109110
.map(|(component_id, trigger_config)| {
111+
let component = trigger_app.get_component(component_id)?;
110112
let handler_type = match &trigger_config.executor {
111113
None | Some(HttpExecutorType::Http) => {
112-
let component = trigger_app.get_component(component_id)?;
113-
HandlerType::from_component(trigger_app.engine().as_ref(), component)?
114+
HandlerType::from_component(component)?
114115
}
115116
Some(HttpExecutorType::Wagi(wagi_config)) => {
116117
anyhow::ensure!(
117118
wagi_config.entrypoint == "_start",
118119
"Wagi component '{component_id}' cannot use deprecated 'entrypoint' field"
119120
);
120-
HandlerType::Wagi
121+
HandlerType::Wagi(CommandIndices::new(component)
122+
.context("failed to find wasi command interface for wagi executor")?)
121123
}
122124
};
123125
Ok((component_id.clone(), handler_type))
@@ -269,20 +271,23 @@ impl<F: RuntimeFactors> HttpServer<F> {
269271
.execute(instance_builder, &route_match, req, client_addr)
270272
.await
271273
}
272-
HandlerType::Wasi0_2
273-
| HandlerType::Wasi2023_11_10
274-
| HandlerType::Wasi2023_10_18 => {
275-
WasiHttpExecutor {
276-
handler_type: *handler_type,
277-
}
278-
.execute(instance_builder, &route_match, req, client_addr)
279-
.await
274+
HandlerType::Wasi0_2(_)
275+
| HandlerType::Wasi2023_11_10(_)
276+
| HandlerType::Wasi2023_10_18(_) => {
277+
WasiHttpExecutor { handler_type }
278+
.execute(instance_builder, &route_match, req, client_addr)
279+
.await
280280
}
281-
HandlerType::Wagi => unreachable!(),
281+
HandlerType::Wagi(_) => unreachable!(),
282282
},
283283
HttpExecutorType::Wagi(wagi_config) => {
284+
let indices = match handler_type {
285+
HandlerType::Wagi(indices) => indices,
286+
_ => unreachable!(),
287+
};
284288
let executor = WagiHttpExecutor {
285-
wagi_config: wagi_config.clone(),
289+
wagi_config,
290+
indices,
286291
};
287292
executor
288293
.execute(instance_builder, &route_match, req, client_addr)
@@ -461,7 +466,7 @@ fn set_req_uri(req: &mut Request<Body>, scheme: Scheme) -> anyhow::Result<()> {
461466
}
462467

463468
/// An HTTP executor.
464-
pub(crate) trait HttpExecutor: Clone + Send + Sync + 'static {
469+
pub(crate) trait HttpExecutor {
465470
fn execute<F: RuntimeFactors>(
466471
&self,
467472
instance_builder: TriggerInstanceBuilder<F>,

crates/trigger-http/src/wagi.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,18 @@ use spin_factor_wasi::WasiFactor;
77
use spin_factors::RuntimeFactors;
88
use spin_http::{config::WagiTriggerConfig, routes::RouteMatch, wagi};
99
use tracing::{instrument, Level};
10+
use wasmtime_wasi::bindings::CommandIndices;
1011
use wasmtime_wasi::pipe::MemoryOutputPipe;
1112
use wasmtime_wasi_http::body::HyperIncomingBody as Body;
1213

1314
use crate::{headers::compute_default_headers, server::HttpExecutor, TriggerInstanceBuilder};
1415

15-
#[derive(Clone)]
16-
pub struct WagiHttpExecutor {
17-
pub wagi_config: WagiTriggerConfig,
16+
pub struct WagiHttpExecutor<'a> {
17+
pub wagi_config: &'a WagiTriggerConfig,
18+
pub indices: &'a CommandIndices,
1819
}
1920

20-
impl HttpExecutor for WagiHttpExecutor {
21+
impl HttpExecutor for WagiHttpExecutor<'_> {
2122
#[instrument(name = "spin_trigger_http.execute_wagi", skip_all, err(level = Level::INFO), fields(otel.name = format!("execute_wagi_component {}", route_match.component_id())))]
2223
async fn execute<F: RuntimeFactors>(
2324
&self,
@@ -87,7 +88,7 @@ impl HttpExecutor for WagiHttpExecutor {
8788

8889
let (instance, mut store) = instance_builder.instantiate(()).await?;
8990

90-
let command = wasmtime_wasi::bindings::Command::new(&mut store, &instance)?;
91+
let command = self.indices.load(&mut store, &instance)?;
9192

9293
tracing::trace!("Calling Wasm entry point");
9394
if let Err(()) = command

crates/trigger-http/src/wasi.rs

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ use anyhow::{anyhow, Context, Result};
55
use futures::TryFutureExt;
66
use http::{HeaderName, HeaderValue};
77
use hyper::{Request, Response};
8-
use spin_factor_outbound_http::wasi_2023_10_18::exports::wasi::http::incoming_handler as incoming_handler2023_10_18;
9-
use spin_factor_outbound_http::wasi_2023_11_10::exports::wasi::http::incoming_handler as incoming_handler2023_11_10;
8+
use spin_factor_outbound_http::wasi_2023_10_18::Proxy as Proxy2023_10_18;
9+
use spin_factor_outbound_http::wasi_2023_11_10::Proxy as Proxy2023_11_10;
1010
use spin_factors::RuntimeFactors;
1111
use spin_http::routes::RouteMatch;
1212
use spin_http::trigger::HandlerType;
@@ -18,12 +18,11 @@ use wasmtime_wasi_http::{bindings::Proxy, body::HyperIncomingBody as Body, WasiH
1818
use crate::{headers::prepare_request_headers, server::HttpExecutor, TriggerInstanceBuilder};
1919

2020
/// An [`HttpExecutor`] that uses the `wasi:http/incoming-handler` interface.
21-
#[derive(Clone)]
22-
pub struct WasiHttpExecutor {
23-
pub handler_type: HandlerType,
21+
pub struct WasiHttpExecutor<'a> {
22+
pub handler_type: &'a HandlerType,
2423
}
2524

26-
impl HttpExecutor for WasiHttpExecutor {
25+
impl HttpExecutor for WasiHttpExecutor<'_> {
2726
#[instrument(name = "spin_trigger_http.execute_wasm", skip_all, err(level = Level::INFO), fields(otel.name = format!("execute_wasm_component {}", route_match.component_id())))]
2827
async fn execute<F: RuntimeFactors>(
2928
&self,
@@ -76,26 +75,22 @@ impl HttpExecutor for WasiHttpExecutor {
7675

7776
enum Handler {
7877
Latest(Proxy),
79-
Handler2023_11_10(incoming_handler2023_11_10::Guest),
80-
Handler2023_10_18(incoming_handler2023_10_18::Guest),
78+
Handler2023_11_10(Proxy2023_11_10),
79+
Handler2023_10_18(Proxy2023_10_18),
8180
}
8281

8382
let handler = match self.handler_type {
84-
HandlerType::Wasi2023_10_18 => {
85-
let indices =
86-
incoming_handler2023_10_18::GuestIndices::new_instance(&mut store, &instance)?;
83+
HandlerType::Wasi2023_10_18(indices) => {
8784
let guest = indices.load(&mut store, &instance)?;
8885
Handler::Handler2023_10_18(guest)
8986
}
90-
HandlerType::Wasi2023_11_10 => {
91-
let indices =
92-
incoming_handler2023_11_10::GuestIndices::new_instance(&mut store, &instance)?;
87+
HandlerType::Wasi2023_11_10(indices) => {
9388
let guest = indices.load(&mut store, &instance)?;
9489
Handler::Handler2023_11_10(guest)
9590
}
96-
HandlerType::Wasi0_2 => Handler::Latest(Proxy::new(&mut store, &instance)?),
91+
HandlerType::Wasi0_2(indices) => Handler::Latest(indices.load(&mut store, &instance)?),
9792
HandlerType::Spin => unreachable!("should have used SpinHttpExecutor"),
98-
HandlerType::Wagi => unreachable!("should have used WagiExecutor instead"),
93+
HandlerType::Wagi(_) => unreachable!("should have used WagiExecutor instead"),
9994
};
10095

10196
let span = tracing::debug_span!("execute_wasi");
@@ -111,12 +106,14 @@ impl HttpExecutor for WasiHttpExecutor {
111106
}
112107
Handler::Handler2023_10_18(handler) => {
113108
handler
109+
.wasi_http0_2_0_rc_2023_10_18_incoming_handler()
114110
.call_handle(&mut store, request, response)
115111
.instrument(span)
116112
.await
117113
}
118114
Handler::Handler2023_11_10(handler) => {
119115
handler
116+
.wasi_http0_2_0_rc_2023_11_10_incoming_handler()
120117
.call_handle(&mut store, request, response)
121118
.instrument(span)
122119
.await

0 commit comments

Comments
 (0)