diff --git a/eng/Versions.props b/eng/Versions.props
index 65b3b238eeeea..69e659feb25d2 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -217,7 +217,7 @@
8.0.0-rc.1.23381.1
- 2.1.7
+ 2.2.2
8.0.0-alpha.1.23180.2
16.0.5-alpha.1.23401.4
diff --git a/src/libraries/System.Net.Quic/src/System.Net.Quic.csproj b/src/libraries/System.Net.Quic/src/System.Net.Quic.csproj
index 281657f4ba5c0..ba05cef37b575 100644
--- a/src/libraries/System.Net.Quic/src/System.Net.Quic.csproj
+++ b/src/libraries/System.Net.Quic/src/System.Net.Quic.csproj
@@ -14,7 +14,7 @@
ExcludeApiList.PNSE.txt
- true
+ false
diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicApi.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicApi.cs
index 193a3db833c14..e07a99b13f7c7 100644
--- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicApi.cs
+++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicApi.cs
@@ -18,7 +18,7 @@ internal sealed unsafe partial class MsQuicApi
{
private static readonly Version s_minWindowsVersion = new Version(10, 0, 20145, 1000);
- private static readonly Version s_minMsQuicVersion = new Version(2, 1);
+ private static readonly Version s_minMsQuicVersion = new Version(2, 2, 2);
private static readonly delegate* unmanaged[Cdecl] MsQuicOpenVersion;
private static readonly delegate* unmanaged[Cdecl] MsQuicClose;
diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Interop/msquic_generated.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Interop/msquic_generated.cs
index 732de3527499c..2fb9196ae707a 100644
--- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Interop/msquic_generated.cs
+++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Interop/msquic_generated.cs
@@ -151,6 +151,7 @@ internal enum QUIC_STREAM_OPEN_FLAGS
NONE = 0x0000,
UNIDIRECTIONAL = 0x0001,
ZERO_RTT = 0x0002,
+ DELAY_FC_UPDATES = 0x0004,
}
[System.Flags]
diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs
index a25ce5b9e4f78..a2ade033afe59 100644
--- a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs
+++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs
@@ -515,6 +515,7 @@ private unsafe int HandleEventPeerStreamStarted(ref PEER_STREAM_STARTED_DATA dat
return QUIC_STATUS_SUCCESS;
}
+ data.Flags |= QUIC_STREAM_OPEN_FLAGS.DELAY_FC_UPDATES;
return QUIC_STATUS_SUCCESS;
}
private unsafe int HandleEventPeerCertificateReceived(ref PEER_CERTIFICATE_RECEIVED_DATA data)
diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs
index 2b18c84055692..503c2d4068355 100644
--- a/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs
+++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs
@@ -709,6 +709,32 @@ ValueTask OpenStreamAsync(QuicConnection connection) => unidirection
await serverConnection.DisposeAsync();
}
+ [Fact]
+ public async Task OpenStreamAsync_BlocksUntilAvailable_PeerClosesWritingUnidirectional()
+ {
+ QuicListenerOptions listenerOptions = new QuicListenerOptions()
+ {
+ ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 0),
+ ApplicationProtocols = new List() { ApplicationProtocol },
+ ConnectionOptionsCallback = (_, _, _) =>
+ {
+ var serverOptions = CreateQuicServerOptions();
+ serverOptions.MaxInboundBidirectionalStreams = 1;
+ serverOptions.MaxInboundUnidirectionalStreams = 1;
+ return ValueTask.FromResult(serverOptions);
+ }
+ };
+ (QuicConnection clientConnection, QuicConnection serverConnection) = await CreateConnectedQuicConnection(null, listenerOptions);
+
+ // Open one stream, second call should block
+ await using var stream = await clientConnection.OpenOutboundStreamAsync(QuicStreamType.Unidirectional);
+ await stream.WriteAsync(new byte[64*1024], completeWrites: true);
+ await Assert.ThrowsAsync(() => clientConnection.OpenOutboundStreamAsync(QuicStreamType.Unidirectional).AsTask().WaitAsync(TimeSpan.FromSeconds(1)));
+
+ await clientConnection.DisposeAsync();
+ await serverConnection.DisposeAsync();
+ }
+
[Theory]
[InlineData(false)]
[InlineData(true)]
@@ -747,11 +773,24 @@ ValueTask OpenStreamAsync(QuicConnection connection, CancellationTok
// Close the streams, the waitTask should finish as a result.
await stream.DisposeAsync();
- QuicStream newStream = await serverConnection.AcceptInboundStreamAsync();
- await newStream.DisposeAsync();
+ // Drain all server streams.
+ while (true)
+ {
+ using var acceptCts = new CancellationTokenSource(TimeSpan.FromSeconds(0.5));
+ try
+ {
+ QuicStream serverStream = await serverConnection.AcceptInboundStreamAsync(acceptCts.Token);
+ await serverStream.DisposeAsync();
+ }
+ catch (OperationCanceledException)
+ {
+ // Token expired, no more streams in the server queue, exit the loop.
+ break;
+ }
+ }
// next call should work as intended
- newStream = await OpenStreamAsync(clientConnection).AsTask().WaitAsync(TimeSpan.FromSeconds(10));
+ var newStream = await OpenStreamAsync(clientConnection).AsTask().WaitAsync(TimeSpan.FromSeconds(10));
await newStream.DisposeAsync();
await clientConnection.DisposeAsync();