Skip to content

proto: Replace write_source with WriteGuard<'_> #2242

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

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

gretchenfrage
Copy link
Collaborator

@gretchenfrage gretchenfrage commented May 13, 2025

This PR is competing and mutually exclusive with my #2230 PR. In the course of developing 2230 I had the idea that this approach could be tried instead. The impact on LOC is similar (this one is slightly better), and this is simpler in some ways (avoids inversion of control). I'm currently creating this as a draft because I haven't thought about it for very long but you're welcome to look at it and see if you think this is better than 2230?

I did some looking and it looks like the write_source API was introduced in #1013. The main aim of that PR seemed to be simply to introduce the ability to directly use Bytes in general. This made me wonder why this wasn't done by adding a method that just wrote one Bytes at a time. From looking at the code, it looks like the main penalty to do this would be duplicated hash table look-ups for the stream. Caching the &mut, as this PR does, avoids that.


This commit removes the ByteSource trait and the write_source method. Instead, a new method is introduced as such:

fn SendStream::write_guard(&mut self) -> Result<WriteGuard<'_>, WriteError>

The resultant WriteGuard contains the limit of how many bytes could be written now, as well as borrows of the fields necessary to perform those writes. The write and write_chunks methods are written to more simply use this.

This commit removes the ByteSource trait and the write_source method.
Instead, a new method is introduced as such:

fn SendStream::write_guard(&mut self) ->
    Result<WriteGuard<'_>, WriteError>

The resultant WriteGuard contains the limit of how many bytes could be
written now, as well as borrows of the fields necessary to perform those
writes. The write and write_chunks methods are written to more simply
use this.
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.

I like this direction -- I think guard types generally make for more idiomatic API than callbacks (whether direct or via a custom trait definition).

@Ralith
Copy link
Collaborator

Ralith commented May 14, 2025

This made me wonder why this wasn't done by adding a method that just wrote one Bytes at a time.

At the quinn level, batched APIs allow the connection lock to be acquired once instead of N times, which can substantially reduce contention.

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.

+1 to this direction.

Comment on lines +391 to +394
stream: &'a mut Send,
data_sent: &'a mut u64,
unacked_data: &'a mut u64,
pending: &'a mut PendingStreamsQueue,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should these be a single reference to a helper struct? That might also be easier to adapt to the quinn layer.

@@ -367,6 +385,28 @@ impl<'a> SendStream<'a> {
}
}

struct WriteGuard<'a> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

bikeshed: WriteBuilder? "Guard" usually means "cleans something up on Drop".

Copy link
Member

Choose a reason for hiding this comment

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

Maybe StreamWriter? Builder invokes builder pattern for construction to me.

}

fn write_source<B: BytesSource>(&mut self, source: &mut B) -> Result<Written, WriteError> {
fn write_guard(&mut self) -> Result<WriteGuard, WriteError> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

bikeshed: build_write, or maybe begin_write.

Ok(self.write_source(&mut ByteSlice::from_slice(data))?.bytes)
let mut guard = self.write_guard()?;
let written = data.len().min(guard.limit);
guard.write(data[..written].to_vec().into());
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
guard.write(data[..written].to_vec().into());
guard.write(Bytes::copy_from_slice(&data[..written]));

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