hyperium / h3

MIT License
571 stars 75 forks source link

WebTransport: stream priority #197

Open kixelated opened 1 year ago

kixelated commented 1 year ago

Hey folks,

I'm working on a proposed live media standard: Media over QUIC. I ported my library from quiche to quinn when I found the h3-webtransport crate. Big thanks to @darioalessandro and @ten3roberts for implementing it!

One thing that got lost in my migration was the ability to prioritize streams. It's critically important for MoQ and it's the key functionality that allows QUIC to deliver live media over congested networks. I specifically want send order, which means streams are transmitted in a strict order based on an integer priority (basically a priority queue).

Quinn implements send order but it is not implemented in the h3::quic trait or the h3_webtransport::SendStream struct. One of the problems is that there's no official QUIC API and nothing is sent over the wire, so stream prioritization is up to the implementation. Here's a few I've seen in the wild:

I think there's two routes to support stream prioritization.

1. Add set_priority to the h3::quic trait.

The debate would primarily be over the API. I'm a huge fan of a i32 (like Quinn) to match the JS API, but that would rule out other QUIC backends as they exist today.

I could work with a u8 like I did with quiche, but it's quite annoying. My application would maintain the stream priority queue and constantly update the stream priority with the index in the queue, limited to 256 streams outstanding at any time. Alternatively, I've discussed changing the quiche API with the authors and they're open to it.

I don't think an incremental flag is useful, even for the HTTP/3 Priorities. incremental=false is a solid default.

2. Add a mechanism to access the underlying SendStream.

The h3::quic trait makes a lot of sense for HTTP/3 since there's a set of core functionality required for HTTP/3. However, WebTransport models itself as QUIC on the web, which means exposing a nebulous "QUIC API". You can envision WebTransport as a thin layer on top of QUIC streams primarily for browser support.

I think it would be reasonable if the application could access the quinn::SendStream directly. For those unaware, WebTransport streams involve a few bytes at the start to identify the session_id. They behave like regular QUIC streams after that, although RESET_STREAM error codes use a special encoding to include the session_id.

I haven't explored if this would work, but maybe h3_webtransport::SendStream could implement inner(&mut self) &mut T or just DerefMut? It would be ideal if the application could not get access to the underlying quinn::SendStream.reset() to avoid misuse, which is why overloading it and using Deref might be better, but I think the h3::quic trait gets in the way.

ten3roberts commented 1 year ago

Accessing the underlying stream could easily lead to issues when invariants are being broken due to a user reading or writing or in other ways modifying the code, which mechanisms in h3 would then need to be aware of. Another issue would be the buffering breaking when the stream can be accessed directly.

I think a similar mechanism as the SendStreamUnframed that I implemented for non-framed data would be the best way to go about this; this way we could extend the functionality of streams like resetting without requiring all the supported quic backends to provide such an api.

I am willing to tackle this, though I think it is up to the owners of h3 to decide which route which is best to take.

Ruben2424 commented 1 year ago

In the design proposal was a RFC mentioned about prioritization.

  • Prioritization: Stream priorities were defined in HTTP/2, and are especially useful for browsers to improve page load speeds. However, they are not specifically defined in QUIC or HTTP/3. There is a later RFC that we should consider instead.

I am not familiar with the RFC so I can not tell at the moment if this also works with Web-Transport or if it fits your use-case. In my opinion, if the RFC doesn't work, the proposed change should be made in h3-webtransport rather than in h3.

About the types maybe we can introduce a associated type at the quic trait for the parameter. So if you would use h3-quinn it is i32 and with a possible quiche implementation of the quic traits it could be a u8.

kixelated commented 1 year ago

I brought up HTTP/3 priorities because it's a standard and has to be implemented at the QUIC layer. See this recent blog post for more info about RFC9218. WebTransport prioritization is completely separate from HTTP/3 prioritization but ideally should share an implementation.

The urgency field in HTTP/3 priorities is basically the WebTransport's proposed send order, but constrained to a u3 (8 values) instead of a i32. The incremental field in HTTP/3 priorities it hard-coded to false in WebTransport, which is to say that you cannot prioritize FIN streams over non-FIN streams.

I've been chatting with @lpardue about adding send-order to HTTP/3 priorities and he wrote up a draft. I'm not sure that anybody cares enough to see it become an RFC though, and I'm preoccupied with WebTransport and not HTTP/3.

And keep in mind that any form of WebTransport/MoQ prioritization scheme could change. I am confident that some form of strict prioritization is the right solution, but the API is still very much up in the air. There's also a lot of small details being worked out, like the policy towards retransmissions. That's why I would argue for a per-implementation API until we're further along to stabilize a trait.