-
Notifications
You must be signed in to change notification settings - Fork 5.4k
Description
QUIC Stream Priority
Background and motivation
- RFC does not specify in what way prioritization should be exposed. It's not transmitted over the wire, it only serves internally to prioritize the order of sent data.
- Every implementation exposes this in a different way. From byte to ushort, with smaller values meaning lower prio in most but also higher prio in some. Default values also differ, but in most cases it's the middle value from the available range.
- MsQuic itself uses ushort with 0x7FFF as default, higher value means higher prio.
API Proposal
QuicStream settable property:
- Makes
QuicStreammutable. - Setter that will end up in P/Invoke, contrary to the expectation that setters are lightweight.
- Thread-safety: in this particular case it's handled by MsQuic.
- Lightweight solution, optional usage, doesn't add any overloads.
- Similar issue dotnet/runtime#121910 will be probably solved the same way, i.e. by a settable property
Priority type and range:
- To use a lowest common denominator,
byteshould be used. - To follow the behavior of majority, middle value 0x7F should be the default, 0x00 lowest priority, 0xFF highest.
- It will need to be translated into MsQuic values, for example leaving it as the "higher" bit:
- 0x00 = 0x00FF
- 0x10 = 0x10FF
- 0x7F = 0x7FFF
- 0xFF = 0xFFFF
namespace System.Net.Quic;
public class QuicStream
{
// Existing property
public long Id { get; }
// The setter implementation will call: SetParam(QUIC_PARAM_STREAM_PRIORITY, (ushort)((value << 8) | 0xFF));
// The getter will return immediately (value backed by a private field).
+ public byte Priority { get; set; }
}API Usage
QuicStream stream = await connection.OpenOutboundStreamAsync(QuicStreamType.Bidirectional);
Console.WriteLine($"Stream {stream} priority is {stream.Priority}");
byte originalPriority = stream.Priority;
stream.Priority = byte.MaxValue; // highest priority
Console.WriteLine($"Stream {stream} priority is {stream.Priority}");
...
stream.Priority = 0; // lowest priority
Console.WriteLine($"Stream {stream} priority is {stream.Priority}");
...
stream.Priority = originalPriority; // back to the defaultAlternative designs
Enum instead of byte
Optionally, an enum like ThreadPriority can be used, or just a set of constants exposed, or an enum backed by byte to define the lowest, default and highest priority
namespace System.Net.Quic;
+public enum QuicStreamPriority : byte
+{
+ Lowest = 0x00,
+ Default = 0x7F,
+ Highest = 0xFF
+}
public class QuicStream
{
// Existing property
public long Id { get; }
// Note that you can set any byte value to the setter.
+ public QuicStreamPriority Priority { get; set; } = QuicStreamPriority.Default;
}
// Usage:
stream.Priority = QuicStreamPriority.Highest;
stream.Priority = 0xAA;
stream.Priority = 0x01;
stream.Priority = QuicStreamPriority.Lowest;
Console.WriteLine($"Stream {stream} priority is {stream.Priority}");QuicStreamOptions
- Follows the same pattern as
QuicConnectionOptions. - Must be provided before creating the stream a parameter to:
- The caller doesn't know yet what stream it is accepting, e.g. in H/3 wanting to change priority of the control stream, but the first accepted stream might be request stream.
- Incurs an extra allocation.
- Does not allow solving dotnet/runtime#121910, i.e. changing default error code after the stream was created and even read from.
- Proposal from the original issue:
namespace System.Net.Quic;
+public sealed class QuicStreamOptions
+{
+ // Should probably be non-nullable with default value, see "Priority type and range" bellow.
+ public byte? Priority { get; set; }
+}
public sealed class QuicConnection
{
// Existing
public ValueTask<QuicStream> AcceptInboundStreamAsync(CancellationToken cancellationToken = default);
// New
+ public ValueTask<QuicStream> AcceptInboundStreamAsync(QuicStreamOptions options, CancellationToken cancellationToken = default);
// Existing
public ValueTask<QuicStream> OpenOutboundStreamAsync(QuicStreamType type, CancellationToken cancellationToken = default);
// New
+ public ValueTask<QuicStream> OpenOutboundStreamAsync(QuicStreamType type, QuicStreamOptions options, CancellationToken cancellationToken = default);
}Original proposal:
Details
MsQuic has support for stream prioritization: https://github.com/microsoft/msquic/blob/main/docs/Settings.md#stream-parameters (QUIC_PARAM_STREAM_PRIORITY)
This is something I'd like to use in my application, so I wanted to first check if there are already existing plans to support this in System.Net.Quic? (I saw there's discussion about adding more options in #72984, but that seems to be specifically about connection-level options.)
If there are no existing plans for this, I can turn this issue into an actual API proposal.
API proposal after discussion
+public sealed class QuicStreamOptions
+{
+ public byte? Priority { get; set; }
+}
public sealed class QuicConnection
{
public ValueTask<QuicStream> AcceptInboundStreamAsync(CancellationToken cancellationToken = default);
+ public ValueTask<QuicStream> AcceptInboundStreamAsync(QuicStreamOptions options, CancellationToken cancellationToken = default);
public ValueTask<QuicStream> OpenOutboundStreamAsync(QuicStreamType type, CancellationToken cancellationToken = default);
+ public ValueTask<QuicStream> OpenOutboundStreamAsync(QuicStreamType type, QuicStreamOptions options, CancellationToken cancellationToken = default);
}Considerations:
- While MsQuic supports
ushortpriority values, quiche only supportsbyte, hencePrioritybeing typed as the latter.- Alternatively, we could make it an
intand just limit the range until such a time that we might want to allow a broader range of values. - Another approach would be to use a
QuicStreamPriorityenum, akin toThreadPriority, but perhaps this is too constraining.
- Alternatively, we could make it an
- Should it be possible to change the stream priority after stream creation? I think both MsQuic and quiche support this.