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

Read ordered chunks from RecvStream #952

Merged
merged 3 commits into from
Jan 6, 2021
Merged

Read ordered chunks from RecvStream #952

merged 3 commits into from
Jan 6, 2021

Conversation

stammw
Copy link
Contributor

@stammw stammw commented Dec 29, 2020

Hello !

I've started trying to implement #929.

Still I'd better be adding some tests before its ready. So I'm setting this PR WIP for a first feedback.

@stammw stammw force-pushed the read-ordered-chunks branch from 99d6a16 to c925a15 Compare December 29, 2020 20:06
hash_map::Entry::Occupied(e) => e,
};
let rs = entry.get_mut();
match rs.read_chunk() {
Copy link
Member

Choose a reason for hiding this comment

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

This is pretty repetitive, can we factor out a bunch of the match here (ideally in a separate commit)? Could apply to read_unordered() as well, ideally, but just between read() and read_chunk() would be good if that makes it hard.

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 got a solution I find a little complex in feb3b63, what do you think about this?

Copy link
Member

Choose a reason for hiding this comment

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

It looks like you can just inline the IntoReadResult into the closure passed to try_read()? We already have the nested map() calls anyway. Or am I missing something?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ho yeah maybe ! 😅
For the moment I get stuck because self.add_read_credits(len as u64) needs to mutate self to compute transmit_max_data, and it is already mutably borrowed by the closure. But I'll try a little harder in a bit.

Comment on lines 1165 to 1173
if let Some(bytes) = self.assembler.read_chunk()? {
Ok(Some(bytes))
} else {
self.read_blocked().map(|()| None)
}
Copy link
Member

Choose a reason for hiding this comment

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

A match expression would be more readable and more compact, I think?

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

Copy link
Collaborator

@Ralith Ralith left a comment

Choose a reason for hiding this comment

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

Thanks for working on this!

@@ -347,6 +347,11 @@ where
}
}

/// [`read_chunk()`]: RecvStream::read_chunk
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should be documented, including an explicit warning against relying on the chunk boundaries having any particular relationship to the sender's writes.

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 got this a better comment. I'm not sure I grasped everything you'd like it to say?

@stammw stammw force-pushed the read-ordered-chunks branch 3 times, most recently from 0f6bece to c1594b4 Compare December 30, 2020 20:32
Ralith
Ralith previously requested changes Dec 30, 2020
Copy link
Collaborator

@Ralith Ralith left a comment

Choose a reason for hiding this comment

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

Wording tweaks for the comments:

ReadChunk { stream: self }
}

/// [`read_chunk()`]: RecvStream::read_chunk
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
/// [`read_chunk()`]: RecvStream::read_chunk
/// Foundation of [`read_chunk()`]: RecvStream::read_chunk

self.poll_read_generic(cx, |conn, stream| conn.inner.read_unordered(stream))
}

/// Read the next segment of data from next valid offset in the stream.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
/// Read the next segment of data from next valid offset in the stream.
/// Read the next segment of data

Comment on lines 378 to 384
/// Yields a segment of data consecutive to the last ordered read offset, or `None`
/// if the stream was finished. Segments are received in order.
///
/// Beware, the returned chunks bounds won't always match those written by the peer.
///
/// Chunk reads have reduced overhead and higher throughput, and should therefore be
/// preferred when applicable.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Tweaked wording a bit:

Suggested change
/// Yields a segment of data consecutive to the last ordered read offset, or `None`
/// if the stream was finished. Segments are received in order.
///
/// Beware, the returned chunks bounds won't always match those written by the peer.
///
/// Chunk reads have reduced overhead and higher throughput, and should therefore be
/// preferred when applicable.
/// Yields a segment of data beginning immediately after the last data yielded by
/// `read` or `read_chunk`, or `None` if the stream was finished.
///
/// Slightly more efficient than `read` due to not copying. Chunks boundaries
/// do not correspond to peer writes, and hence cannot be used as framing.

Comment on lines 871 to 872
/// window is filled. When in-order delivery is required, the siblings `read()` or `read_chunk()`
/// method should be used.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
/// window is filled. When in-order delivery is required, the siblings `read()` or `read_chunk()`
/// method should be used.
/// window is filled. When in-order delivery is required, the sibling `read()` or `read_chunk()`
/// methods should be used.

@djc
Copy link
Member

djc commented Jan 2, 2021

(Note that #961 is blocked on this, so would be nice to follow through soon...)

@Ralith
Copy link
Collaborator

Ralith commented Jan 2, 2021

My wording in #961 was unclear--I don't actually consider it blocked, I'm just holding off on adding additional refactoring that would make rebasing this PR harder than it otherwise would be.

@Matthias247
Copy link
Contributor

While we are at it: I would also appreciate a vectored read API - which can retrieve more than 1 chunk at a time. Because locking a Mutex and potentially doing a task wakeup for reading just 1kB of data (due to Quic packet sizes) is fairly unefficient.

One API I've used in a project for that is:

async fn read(&mut self, &mut [Bytes]) -> Result<(usize, bool), Error>

Where the caller provides an array (initialized with dummy elements / Bytes::new()) - and the implementation fills as many array items as possible and returns the number of chunks read in the first return value. Second return value is for indicating end of stream.

@stammw stammw force-pushed the read-ordered-chunks branch from 66e55a5 to 34b892c Compare January 4, 2021 22:24
@stammw
Copy link
Contributor Author

stammw commented Jan 4, 2021

I finally got the read methods factorization right (yeah the IntoResult thing was weird...).

I've addesd a test for assembler::read_chunk() in the first commit.

All the wording changes have been addressed.

A new commit contains @Matthias247 suggestion. This is still WIP as I'm waiting for your feedback on this public API change, and it should probably be tested in the functional test module.

@djc
Copy link
Member

djc commented Jan 4, 2021

Did you see #955?

@stammw stammw force-pushed the read-ordered-chunks branch from 34b892c to cad6f1a Compare January 4, 2021 22:43
@stammw
Copy link
Contributor Author

stammw commented Jan 4, 2021

I should probably have paid more attention, sorry... Now the IntoResult solution is totally removed here. I'm a little too tired to judge which one is better.

@stammw stammw force-pushed the read-ordered-chunks branch from cad6f1a to 4840713 Compare January 4, 2021 22:54
@djc
Copy link
Member

djc commented Jan 4, 2021

Maybe @Ralith or @Matthias247 want to take a look which version they prefer.


if chunk.offset > self.bytes_read {
// Next chunk is after current read index
return Ok(None);
Copy link
Contributor

Choose a reason for hiding this comment

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

Isn't None end of stream? i'm not sure how this function is used, but it might need to return a blocked status instead.

Copy link
Contributor Author

@stammw stammw Jan 6, 2021

Choose a reason for hiding this comment

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

This method works as it's siblings: it returns None when there is nothing to assemble. Blocked status is managed at the stream level in quinn-proto::connection::streams::Streams::read_chunk().

See the chunks_dedup() test at the end of this file for an example of expected behavior.

@Matthias247
Copy link
Contributor

Maybe @Ralith or @Matthias247 want to take a look which version they prefer.

I skimmed both commits, and I have a slight preference for this one since I found the ReadResult/StreamReadResult/IntoReadResult types a bit harder to follow.

I think this change currently has a good balance between code reuse and readability.

@stammw Also thanks for incorporating my feedback!

@stammw stammw force-pushed the read-ordered-chunks branch from 4840713 to 6fbfc6e Compare January 6, 2021 06:53
@stammw
Copy link
Contributor Author

stammw commented Jan 6, 2021

The current read methods factorization solution (without IntoResult) is actually better IMO.

I've added a test for read_chunks(), so this is ready to be merged if you guys like the current state of this PR.

@stammw stammw changed the title WIP: Read ordered chunks from RecvStream Read ordered chunks from RecvStream Jan 6, 2021
Copy link
Member

@djc djc left a comment

Choose a reason for hiding this comment

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

LGTM, modulo small doc nits.

Comment on lines 381 to 382
/// Slightly more efficient than `read` due to not copying. Chunks boundaries
/// do not correspond to peer writes, and hence cannot be used as framing.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/// Slightly more efficient than `read` due to not copying. Chunks boundaries
/// do not correspond to peer writes, and hence cannot be used as framing.
/// Slightly more efficient than `read` due to not copying. Chunk boundaries
/// do not correspond to peer writes, and hence cannot be used as framing.

Comment on lines 398 to 399
/// Slightly more efficient than `read` due to not copying. Chunks boundaries
/// do not correspond to peer writes, and hence cannot be used as framing.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/// Slightly more efficient than `read` due to not copying. Chunks boundaries
/// do not correspond to peer writes, and hence cannot be used as framing.
/// Slightly more efficient than `read` due to not copying. Chunk boundaries
/// do not correspond to peer writes, and hence cannot be used as framing.

@stammw stammw force-pushed the read-ordered-chunks branch from 6fbfc6e to 6eeda35 Compare January 6, 2021 19:13
@stammw
Copy link
Contributor Author

stammw commented Jan 6, 2021

Should be ok now.

Happy to contribute to quinn a bit :)

@djc djc dismissed Ralith’s stale review January 6, 2021 20:51

Issues solved

@djc djc merged commit 69c620a into main Jan 6, 2021
@djc djc deleted the read-ordered-chunks branch January 6, 2021 20:51
@Matthias247 Matthias247 mentioned this pull request Jan 22, 2021
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