Skip to content
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

Make streams owned by request/response that they are tied to. #6228

Merged
merged 6 commits into from
Apr 21, 2023

Conversation

brendandburns
Copy link
Contributor

This is related to the discussion here: WebAssembly/wasi-http#24

Where we decided that streams should be child-own properties of the request or response that they are tied to.

This adds clarity to when it is valid to drop an input or output stream and in what order relative to the response.

@brendandburns brendandburns requested a review from a team as a code owner April 18, 2023 04:15
@brendandburns brendandburns requested review from alexcrichton and removed request for a team April 18, 2023 04:15
@brendandburns
Copy link
Contributor Author

cc @pchickey

@brendandburns
Copy link
Contributor Author

fwiw, looks like tests failed b/c of a network failure pulling the wasi-http submodule.

@pchickey
Copy link
Contributor

I hit rerun and now it got to a test failure

@alexcrichton
Copy link
Member

I'm going to move this to @pchickey's review queue as he's more familiar with the implementation than I

@alexcrichton alexcrichton requested review from pchickey and removed request for alexcrichton April 18, 2023 18:29
@@ -123,7 +123,13 @@ impl crate::types::Host for WasiHttp {
bail!("unimplemented: drop_incoming_request")
}
fn drop_outgoing_request(&mut self, request: OutgoingRequest) -> wasmtime::Result<()> {
self.requests.remove(&request);
match self.requests.get(&request) {
Copy link
Member

Choose a reason for hiding this comment

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

nit; since we only care about the Some case, how about an if let Some(r) = self.requests.get(&request) instead?

Or even

if let Entry::Occupied(e) = self.requests.entry(request) {
   let r = e.remove();

https://doc.rust-lang.org/std/collections/hash_map/struct.OccupiedEntry.html#method.remove

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Thanks for the suggestion, I'm still learning idiomatic rust)

@@ -2,6 +2,12 @@ use crate::types::{Method, Scheme};
use bytes::Bytes;
use std::collections::HashMap;

#[derive(Clone)]
Copy link
Member

Choose a reason for hiding this comment

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

How about a derive(Default)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done.

@@ -66,6 +72,15 @@ impl ActiveResponse {
}
}

impl Stream {
pub fn new() -> Self {
Self {
Copy link
Member

Choose a reason for hiding this comment

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

If we had a Default trait impl, this could have just called Self::default()

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done.

let st = self
.streams
.get_mut(&stream)
.ok_or_else(|| anyhow!("stream not found: {stream}"))?;
Copy link
Member

Choose a reason for hiding this comment

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

Doesn't with_context work here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure I'm following your suggestion, can you clarify?

Copy link
Member

Choose a reason for hiding this comment

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

Sure, I'm referring to this method on the anyhow::Context trait https://docs.rs/anyhow/latest/anyhow/trait.Context.html#tymethod.with_context

If it's in scope, you should be able to call context or with_context (usually used with format! and similar) on most Result and all Option types directly, thus eliminating the need for ok_or_else and explicit anyhow! invocation

Copy link
Contributor

@pchickey pchickey Apr 20, 2023

Choose a reason for hiding this comment

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

@rvolosatovs I made those ok_or_else(|| anyhow!(...)) suggestions in a prior PR and wasn't aware that Context could make that more succinct. Thank you.

@brendandburns The suggested change here is let st = self.streams.get_mut(&stream).with_context(|| format!("stream not found: {stream}"))?;, or maybe even ....get_mut(&stream).context("stream not found")? if leaving out the stream number is ok - and it probably is, because (at least when using the component linker, I'm not sure if it works in module mode) bindgen is inserting tracing statements which someone debugging can recover the values of the arguments from.

this,
Stream {
closed: false,
data: bytes::Bytes::from(buf.clone()),
Copy link
Member

Choose a reason for hiding this comment

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

You wouldn't need this clone if the call to buf.len below was moved higher, before the match.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done.

@@ -183,7 +183,8 @@ impl WasiHttp {
let body = Full::<Bytes>::new(
self.streams
.get(&request.body)
.unwrap_or(&Bytes::new())
.unwrap_or(&Stream::new())
Copy link
Member

Choose a reason for hiding this comment

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

If we had Default implemented, this could have been unwrap_or_default

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unfortunately, HashMap::get(...) returns a reference rather than a value, and there's no default for a reference. I could implement the Default trait for &Stream references, but I'm not sure if that's what is best. Let me know.

Copy link
Member

Choose a reason for hiding this comment

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

That makes sense, I think this is fine.
I am making a few changes here to allow for the host to provide a custom connect implementation, so I'll take a deeper look at this as well next week

Copy link
Member

Choose a reason for hiding this comment

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

@@ -2,6 +2,12 @@ use crate::types::{Method, Scheme};
use bytes::Bytes;
use std::collections::HashMap;

#[derive(Clone)]
pub struct Stream {
Copy link
Member

Choose a reason for hiding this comment

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

Looking at the usages of this struct, it seems that a From<Bytes> implementation could really clear this up

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added.

self.streams.insert(this, new.freeze());
self.streams.insert(
this,
Stream {
Copy link
Member

Choose a reason for hiding this comment

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

If there was a From implementation, this could have been Stream::from(new.freeze())

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done.

@brendandburns
Copy link
Contributor Author

Comments generally addressed, couple where I need some more feedback.

Please take another look.

Thanks!

Copy link
Member

@rvolosatovs rvolosatovs left a comment

Choose a reason for hiding this comment

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

Just a general question, why are we not modifying the stream data buffer, but rather cloning it? In other words, why would we ever want to read the same bytes more than once?

self.streams.insert(response.body, buf.freeze());
self.streams.insert(
response.body,
Stream {
Copy link
Member

Choose a reason for hiding this comment

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

This should probably also use the From trait

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done.

@brendandburns
Copy link
Contributor Author

I'm not sure I follow your question. Are you asking why we don't use BytesMut instead of a Bytes in the Stream struct? We could do that if we wanted to, I'm not sure it actually matters that much because we'd need to turn it back into a single contiguous array of bytes at some point, but it might be more efficient.

Let me know if that's the change you are suggesting, or if I'm missing something.

Thanks!

crates/wasi-http/src/streams_impl.rs Outdated Show resolved Hide resolved
@pchickey
Copy link
Contributor

@brendandburns I can merge this when ready, please ping when the above discussion is resolved.

bail!("stream is dropped!");
}
let new_len = st.data.len() + len;
let mut new = bytes::BytesMut::with_capacity(new_len);
Copy link
Member

@rvolosatovs rvolosatovs Apr 20, 2023

Choose a reason for hiding this comment

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

From #6228 (comment), as you said, I think I'd just use BytesMut and https://docs.rs/bytes/latest/bytes/struct.BytesMut.html#method.extend_from_slice here

Since that would be quite a bit shorter, it could probably also make sense to remove the match altogether and instead rely on https://doc.rust-lang.org/std/collections/hash_map/enum.Entry.html#method.and_modify (see example there with or_insert)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. I needed to use .or_default().extend_from_slice(...) but I think it achieves the same goal (and thanks for the suggestion of implementing Default that made it possible :)

@brendandburns
Copy link
Contributor Author

@pchickey I think this is ready to merge.

@pchickey pchickey added this pull request to the merge queue Apr 21, 2023
Merged via the queue into bytecodealliance:main with commit 43ec481 Apr 21, 2023
afonso360 pushed a commit to afonso360/wasmtime that referenced this pull request Apr 24, 2023
…dealliance#6228)

* Make streams owned by request/response that they are tied to.

* Address comments, fix tests.

* Address comment.

* Update crates/wasi-http/src/streams_impl.rs

Co-authored-by: Pat Hickey <[email protected]>

* Switch to BytesMut

---------

Co-authored-by: Pat Hickey <[email protected]>
eduardomourar pushed a commit to eduardomourar/wasmtime that referenced this pull request Apr 28, 2023
…dealliance#6228)

* Make streams owned by request/response that they are tied to.

* Address comments, fix tests.

* Address comment.

* Update crates/wasi-http/src/streams_impl.rs

Co-authored-by: Pat Hickey <[email protected]>

* Switch to BytesMut

---------

Co-authored-by: Pat Hickey <[email protected]>
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.

4 participants