From 678d0ff764350761f81d664c629fc195a1aba831 Mon Sep 17 00:00:00 2001 From: ManickaP Date: Wed, 22 Apr 2026 11:43:10 +0200 Subject: [PATCH] Add QuicStream.Priority property Implements the api-approved design from dotnet/runtime#90281. Adds a settable Priority property (byte) to QuicStream with DefaultPriority const (0x7F). The setter translates the byte value to MsQuic's ushort priority via (value << 8) | 0xFF. Includes functional tests for default value, set/get, multiple sets, and ObjectDisposedException after dispose. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../System.Net.Quic/ref/System.Net.Quic.cs | 2 + .../src/System/Net/Quic/QuicStream.cs | 30 ++++++ .../tests/FunctionalTests/QuicStreamTests.cs | 91 +++++++++++++++++++ 3 files changed, 123 insertions(+) diff --git a/src/libraries/System.Net.Quic/ref/System.Net.Quic.cs b/src/libraries/System.Net.Quic/ref/System.Net.Quic.cs index a0c1f488e04785..d8dbf635bef66d 100644 --- a/src/libraries/System.Net.Quic/ref/System.Net.Quic.cs +++ b/src/libraries/System.Net.Quic/ref/System.Net.Quic.cs @@ -114,6 +114,7 @@ public QuicServerConnectionOptions() { } public sealed partial class QuicStream : System.IO.Stream { internal QuicStream() { } + public const byte DefaultPriority = (byte)127; public override bool CanRead { get { throw null; } } public override bool CanSeek { get { throw null; } } public override bool CanTimeout { get { throw null; } } @@ -121,6 +122,7 @@ internal QuicStream() { } public long Id { get { throw null; } } public override long Length { get { throw null; } } public override long Position { get { throw null; } set { } } + public byte Priority { get { throw null; } set { } } public System.Threading.Tasks.Task ReadsClosed { get { throw null; } } public override int ReadTimeout { get { throw null; } set { } } public System.Net.Quic.QuicStreamType Type { get { throw null; } } diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicStream.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicStream.cs index 91d84e6ebf525d..b9a9b8df071951 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicStream.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicStream.cs @@ -120,6 +120,7 @@ public sealed partial class QuicStream private long _id = -1; private readonly QuicStreamType _type; + private byte _priority = DefaultPriority; /// /// Provided via from so that can decrement its available stream count field. @@ -127,6 +128,14 @@ public sealed partial class QuicStream /// private Action? _decrementStreamCapacity; + /// + /// The default value of , which is the middle of the range. + /// + /// + /// represents the lowest priority and represents the highest priority. + /// + public const byte DefaultPriority = 0x7F; + /// /// Stream id, see . /// @@ -137,6 +146,27 @@ public sealed partial class QuicStream /// public QuicStreamType Type => _type; + /// + /// Gets or sets the stream priority, see RFC 9000: Stream Prioritization. + /// + /// + /// Priority only affects the order of data sent on the wire relative to other streams on the same connection. + /// represents the lowest priority and represents the highest. + /// The default value is . + /// + /// The priority level for this stream. The default value is . + public byte Priority + { + get => _priority; + set + { + ObjectDisposedException.ThrowIf(_disposed, this); + + SetMsQuicParameter(_handle, QUIC_PARAM_STREAM_PRIORITY, (ushort)((value << 8) | 0xFF)); + _priority = value; + } + } + /// /// A that will get completed once reading side has been closed. /// Which might be by reading till end of stream ( will return 0), diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamTests.cs index 1996c97831bc3c..dbf4861bdb862e 100644 --- a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamTests.cs +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamTests.cs @@ -1553,5 +1553,96 @@ async ValueTask WaitingSide(QuicStream stream, Task task, bool server) } } } + + [Fact] + public async Task Priority_DefaultValue() + { + await RunClientServer( + serverFunction: async connection => + { + await using QuicStream stream = await connection.AcceptInboundStreamAsync(); + Assert.Equal(QuicStream.DefaultPriority, stream.Priority); + }, + clientFunction: async connection => + { + await using QuicStream stream = await connection.OpenOutboundStreamAsync(QuicStreamType.Bidirectional); + Assert.Equal(QuicStream.DefaultPriority, stream.Priority); + stream.CompleteWrites(); + } + ); + } + + [Theory] + [InlineData(byte.MinValue)] + [InlineData(1)] + [InlineData(QuicStream.DefaultPriority)] + [InlineData(byte.MaxValue)] + public async Task Priority_SetGet(byte priority) + { + await RunClientServer( + serverFunction: async connection => + { + await using QuicStream stream = await connection.AcceptInboundStreamAsync(); + byte[] buffer = new byte[1]; + await ReadAll(stream, buffer); + }, + clientFunction: async connection => + { + await using QuicStream stream = await connection.OpenOutboundStreamAsync(QuicStreamType.Bidirectional); + stream.Priority = priority; + Assert.Equal(priority, stream.Priority); + await stream.WriteAsync(new byte[] { 42 }, completeWrites: true); + } + ); + } + + [Fact] + public async Task Priority_SetMultipleTimes() + { + await RunClientServer( + serverFunction: async connection => + { + await using QuicStream stream = await connection.AcceptInboundStreamAsync(); + byte[] buffer = new byte[1]; + await ReadAll(stream, buffer); + }, + clientFunction: async connection => + { + await using QuicStream stream = await connection.OpenOutboundStreamAsync(QuicStreamType.Bidirectional); + + stream.Priority = byte.MaxValue; + Assert.Equal(byte.MaxValue, stream.Priority); + + stream.Priority = byte.MinValue; + Assert.Equal(byte.MinValue, stream.Priority); + + stream.Priority = QuicStream.DefaultPriority; + Assert.Equal(QuicStream.DefaultPriority, stream.Priority); + + await stream.WriteAsync(new byte[] { 42 }, completeWrites: true); + } + ); + } + + [Fact] + public async Task Priority_ThrowsAfterDispose() + { + await RunClientServer( + serverFunction: async connection => + { + await using QuicStream stream = await connection.AcceptInboundStreamAsync(); + byte[] buffer = new byte[1]; + await ReadAll(stream, buffer); + }, + clientFunction: async connection => + { + QuicStream stream = await connection.OpenOutboundStreamAsync(QuicStreamType.Bidirectional); + await stream.WriteAsync(new byte[] { 42 }, completeWrites: true); + await stream.DisposeAsync(); + + Assert.Throws(() => stream.Priority = byte.MaxValue); + } + ); + } } }