Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/libraries/System.Net.Quic/ref/System.Net.Quic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,13 +114,15 @@ 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; } }
public override bool CanWrite { get { throw null; } }
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; } }
Expand Down
30 changes: 30 additions & 0 deletions src/libraries/System.Net.Quic/src/System/Net/Quic/QuicStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,13 +120,22 @@ public sealed partial class QuicStream

private long _id = -1;
private readonly QuicStreamType _type;
private byte _priority = DefaultPriority;

/// <summary>
/// Provided via <see cref="StartAsync(Action{QuicStreamType}, CancellationToken)" /> from <see cref="QuicConnection" /> so that <see cref="QuicStream"/> can decrement its available stream count field.
/// When <see cref="HandleEventStartComplete(ref START_COMPLETE_DATA)">START_COMPLETE</see> arrives it gets invoked and unset back to <c>null</c> to not to hold any unintended reference to <see cref="QuicConnection"/>.
/// </summary>
private Action<QuicStreamType>? _decrementStreamCapacity;

/// <summary>
/// The default value of <see cref="Priority"/>, which is the middle of the <see cref="byte"/> range.
/// </summary>
/// <remarks>
/// <see cref="byte.MinValue"/> represents the lowest priority and <see cref="byte.MaxValue"/> represents the highest priority.
/// </remarks>
public const byte DefaultPriority = 0x7F;

/// <summary>
/// Stream id, see <see href="https://www.rfc-editor.org/rfc/rfc9000.html#name-stream-types-and-identifier" />.
/// </summary>
Expand All @@ -137,6 +146,27 @@ public sealed partial class QuicStream
/// </summary>
public QuicStreamType Type => _type;

/// <summary>
/// Gets or sets the stream priority, see <see href="https://www.rfc-editor.org/rfc/rfc9000.html#name-stream-prioritization">RFC 9000: Stream Prioritization</see>.
/// </summary>
/// <remarks>
/// <para>Priority only affects the order of data sent on the wire relative to other streams on the same connection.
/// <see cref="byte.MinValue"/> represents the lowest priority and <see cref="byte.MaxValue"/> represents the highest.
/// The default value is <see cref="DefaultPriority"/>.</para>
/// </remarks>
/// <value>The priority level for this stream. The default value is <see cref="DefaultPriority"/>.</value>
public byte Priority
{
get => _priority;
set
{
ObjectDisposedException.ThrowIf(_disposed, this);

SetMsQuicParameter(_handle, QUIC_PARAM_STREAM_PRIORITY, (ushort)((value << 8) | 0xFF));
_priority = value;
}
}

/// <summary>
/// A <see cref="Task"/> that will get completed once reading side has been closed.
/// Which might be by reading till end of stream (<see cref="ReadAsync(System.Memory{byte},System.Threading.CancellationToken)"/> will return <c>0</c>),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1553,5 +1553,96 @@ async ValueTask WaitingSide(QuicStream stream, Task<long?> 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);
}
);
}

Comment thread
ManickaP marked this conversation as resolved.
[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<ObjectDisposedException>(() => stream.Priority = byte.MaxValue);
}
);
}
}
}
Loading