Skip to content

Make PacketBuilder own the write buffer #2195

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

Open
wants to merge 19 commits into
base: main
Choose a base branch
from

Conversation

flub
Copy link
Contributor

@flub flub commented Apr 3, 2025

This is a continuation of #2168 and concentrates on further simplifying the logic needed in the poll_transmit function.

  • The buffer to write into is now owned by the PacketBuilder while building a packet.
  • This allows using BufMut::remaining_mut to know how much space is still available to write for frames. The intermediate BufLen trait is no longer needed for building packets.
  • The poll_transmit function and it's helpers that write frames now no longer deals with offsets into an abstract buffer. It removes the amount of state they need to track and the logical conditions end up being simpler.
  • The main loop has been modified: the goal here was to finish the packet before the loop re-started.
    • This allows the PacketBuilder to own the buffer, which is what allows using BufMut::remaining_mut for the buffer size.
    • It also reduces the amount of state that needs to be carried between loop iterations. Which reduces the cognitive load for readers.
    • Finally, my hope is that this loop construction with .next_send_space() is easier to adapt to mutlipath. Though that's kind of out-of scope and not yet proven.

The downside of using BufMut::limit is that bytes' Limit (and in general BufMut) will panic if you try to write more than allowed. This is an invariant that should not occur, though in general poll_transmit can not propagate errors or even close the connection. So while a custom trait could allow us to survive this we'd still build an invalid packet and then who knows what the remaining state of the connection would be. Perhaps it could be interesting to some day allow poll_transmit to return an error which would result in the connection being closed. But for now I think this choice of BufMut::remaining_mut is fine.

What's next?

The TransmitBuf still exposes absolute offsets which are used by both PacketBuilder and PartialEncode. I think the logic of PacketBuilder and PartialEncode could probably be further simplified by taking the same approach there and not use absolute offsets in the TransmitBuf. Though I've limited the scope of this PR to mainly improving poll_transmit, as this needs more maintenance that the packet building. Just as for #2168, this PR should not be judged on what could happen next.


This PR is on top of #2168. Unfortunately I can't target that PR since the branch does not live in the quinn-rs/quinn repo. So you get the entire diff of #2168 and this together.

This PR starts at Finish packets at end of each poll_transmit loop (currently e7ea038)

flub added 18 commits April 2, 2025 17:41
This TransmitBuf is a wrapper around the buffer in which datagrams are
being created.  It keeps track of the state required to know the
boundaries of the datagrams in the buffer.
This removes the extra arguments from Packetbuilder::new that tell the
builder about datagram boundaries in favour of using the state of
TransmitBuf directly.
This moves the logic of updating the buffer for new datagrams into a
function on the TransmitBuf.  This centralises all the manipulation of
these variables into one logical place, ensuring all the invariants
are upheld.
This helps encapsulation by ensuring that no outside users can mutate
these fields.  Reducing the amount of logic that needs to be reasoned
about.
We want to hide the actual buffer implementation from most of these
locations.  Currently it is a TransmitBuf but it likely will become
something else in the future.  So make this generic.
This allows making TransmitBuf::buf private at last.  Now all the
logic it handles is fully encapsulated.
This re-arranges the loop in poll_transmit to always finish the packet
before going to the next iteration.

This primarily enables to mutably borrow the TransmitBuf into a
packet-specific buffer while the packet is being built.  But this is
not yet utilised in this commit.

It does however remove the need of the mutable builder_storage Option,
which makes reasoning over packet building slightly easier.

- The logic to know on which packet space to send next, or whether
  there is no longer anything to send, has been moved to the
  next_send_space method.

- The logic to decide whether to pad a packet before finishing it is
  moved to the end of the loop.

- The logic to check the congestion controller and pacing is kept at
  the start of the loop.  Before a new packet is started.  Starting a
  new datagram also stays there.
Now that the packet is always finished at the end of the loop we no
longer need to carry around the mutable SentFrames.  Reducing further
the number of things to keep track of.
The PacketBuilder::finish_and_track function took a SentFrames
argument as an Option.  However this was an artifact of the
poll_transmit loop tracking it in an Option before, and it not being
clear this was always Some when things were going correctly.  Now all
the callers clearly always pass this in so we can remove the Option.
Now the lifetimes allow for this the PacketBuilder can own the
TransmitBuf.  This is gives it more control over the buffer into which
the packet can be written.

This commit itself does nothing interesting with this yet.  It merely
moves the buffer ownership in a mechanical way.  However, this enables
future changes to reduce the use of offsets in so many places.
This moves keeping track of the available frame space to the packet
builder.  The available space is now encoded into the BufMut returned
by PacketBuilder::frame_space_mut().  This removes the need for the
BufLen trait in many of the places writing frames.
Now that the PacketBuilder::frame_space_mut exists the direct BufMut
impl on it can be removed.  Nothing external needs to directly write
into the packet buffer outside of the frame space.
This trait is still the simplest way for the PartialEncode to keep
track of the length of the header it just wrote.
It no longer needs to keep track of this field because the TransmitBuf
already does.  Removing duplicate state is nice.
This logic is internal business of the PacketBuilder.  Use the
provided abstraction.
This is duplicate information that is already available.
This starts removing the usage of absolute offsets in the TransmitBuf.
It simplifies the resulting logic in the poll_transmit function.

The absolute offsets are still available since the PacketBuilder still
uses those.
You always need to remember to handle pad_datagram if needed.  While
really this always happens just before the call to finish_and_track.
Instead this can be done in finish_and_track without any logic change,
and this helps avoiding mistakes.
@flub flub changed the title Make PacketBuilder own the wite buffer Make PacketBuilder own the write buffer Apr 3, 2025
flub added a commit to n0-computer/quinn that referenced this pull request Apr 11, 2025
This applies the refactors from
quinn-rs#2168 and
quinn-rs#2195 onto our multipath branch!
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.

1 participant