Skip to content

enable connection pooling and reuse for outbound wasi-http #3229

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 15, 2025

Conversation

dicej
Copy link
Contributor

@dicej dicej commented Aug 13, 2025

This updates factor-outbound-http/wasi.rs to use hyper_util::client::legacy::Client, which does connection pooling by default. Client requires a tower_service::Service implementation for creating new connections, which in turn required moving code around to accomodate a different flow of control. It also forces us to work at a slightly higher level of abstraction, so we don't have as much fine-grained control as before, notably:

  • We can't make HTTP/2-specific request tweaks like we did before (e.g. removing or adding host headers, or tweaking the path-with-query). I'm mainly leaving it up to hyper-util to do the right thing here 🤞.

  • We can't provide an AbortOnDropJoinHandle when constructing an IncomingResponse object, which may mean the connection continues to exist after the response is dropped, but that's kind of the point of connection pooling anyway.

Open questions: do we want to make this configurable, and if so, should it be enabled or disabled by default?

This updates `factor-outbound-http/wasi.rs` to use
`hyper_util::client::legacy::Client`, which does connection pooling by default.
`Client` requires a `tower_service::Service` implementation for creating new
connections, which in turn required moving code around to accomodate a different
flow of control.  It also forces us to work at a slightly higher level of
abstraction, so we don't have as much fine-grained control as before, notably:

- We can't make HTTP/2-specific request tweaks like we did before (e.g. removing
  or adding `host` headers, or tweaking the path-with-query).  I'm mainly
  leaving it up to `hyper-util` to do the right thing here :crossed_fingers:.

- We can't provide an `AbortOnDropJoinHandle` when constructing an
  `IncomingResponse` object, which may mean the connection continues to exist
  after the response is dropped, but that's kind of the point of connection
  pooling anyway.

Open questions: do we want to make this configurable, and if so, should it be
enabled or disabled by default?

Signed-off-by: Joel Dice <[email protected]>
@dicej dicej requested review from fibonacci1729 and lann August 13, 2025 20:53
@dicej
Copy link
Contributor Author

dicej commented Aug 13, 2025

Fixes #2444

@fibonacci1729
Copy link
Contributor

Verified no regressions w.r.t outbound-http2/gRPC

It's enabled by default, but can be disabled via a runtime config file
containing:

```toml
[outbound_http]
connection_pooling = false
```

Signed-off-by: Joel Dice <[email protected]>
Comment on lines +151 to +153
http1: builder().build(HttpConnector),
http2: builder().http2_only(true).build(HttpConnector),
https: builder().build(HttpsConnector),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Builder::build takes &self (cloning internally) so you could reuse one builder here if you wanted. 🤷

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, but then the code would be order-dependent -- I'd have to be sure to build the http2 one last so that the http2_only setting doesn't affect the other ones. I'd prefer to contain the side effect by using separate builders unless there's a good reason not to.

Comment on lines +181 to +184
// We must use task-local variables for these config options when using
// `hyper_util::client::legacy::Client::request` because there's no way to plumb
// them through as parameters. Moreover, if there's already a pooled connection
// ready, we'll reuse that and ignore these options anyway.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd probably take the tiny theoretical performance hit and use a OnceLock for building the clients to avoid this.

Copy link
Contributor Author

@dicej dicej Aug 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not seeing how a OnceLock would avoid the need for task-local variables. We only build the clients once per instance, but each outbound request could have its own connect_timeout, for example. I.e. we can't just build a client with a fixed connect_timeout and use it for all requests. Or am I misunderstanding what you're proposing?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahhh OK yes that makes sense. I think I'm seeing some of the reasons why this is "legacy" 😬

@@ -114,13 +164,198 @@ impl WasiHttpView for WasiHttpImplInner<'_> {
self.state.request_interceptor.clone(),
self.state.self_request_origin.clone(),
self.state.blocked_networks.clone(),
http_clients,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is mostly out of scope so feel free to ignore but it seems clear that these state fields should clearly be their own struct with send_request as a method. I have no idea why I didn't do that last time I was in here...

This will hopefully fix CI 🤞

Signed-off-by: Joel Dice <[email protected]>
@dicej dicej merged commit 10f7840 into spinframework:main Aug 15, 2025
32 of 33 checks passed
@dicej dicej deleted the outbound-wasi-http-connection-pooling branch August 15, 2025 01:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants