From 5fc93f425ead47546efcbe8fa1be0b1becc03c25 Mon Sep 17 00:00:00 2001 From: Natalia Kondratyeva Date: Wed, 26 May 2021 18:08:25 +0200 Subject: [PATCH] [QUIC] Handle connection abort in streams (#52776) This leverages newly added flag to SHUTDOWN_COMPLETED event, which means that's not a user-initiated stream shutdown, but a connection abort. Fixes #32050 --- src/libraries/System.Net.Quic/readme.md | 2 +- .../MsQuic/Interop/MsQuicNativeMethods.cs | 9 + .../MsQuic/MsQuicConnection.cs | 36 ++-- .../Implementations/MsQuic/MsQuicStream.cs | 183 +++++++++++++++--- .../Implementations/MsQuic/ThrowHelper.cs | 26 +++ .../tests/FunctionalTests/MsQuicTests.cs | 56 +++++- .../tests/FunctionalTests/QuicStreamTests.cs | 30 --- 7 files changed, 276 insertions(+), 66 deletions(-) create mode 100644 src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/ThrowHelper.cs diff --git a/src/libraries/System.Net.Quic/readme.md b/src/libraries/System.Net.Quic/readme.md index 050bdb891e062..f3b27a0fa42f6 100644 --- a/src/libraries/System.Net.Quic/readme.md +++ b/src/libraries/System.Net.Quic/readme.md @@ -1,7 +1,7 @@ # MsQuic `System.Net.Quic` depends on [MsQuic](https://github.com/microsoft/msquic), Microsoft, cross-platform, native implementation of the [QUIC](https://datatracker.ietf.org/wg/quic/about/) protocol. -Currently, `System.Net.Quic` depends on [**msquic@2084736032ec917f1819802caa515e61a6d3dd9a**](https://github.com/microsoft/msquic/commit/2084736032ec917f1819802caa515e61a6d3dd9a) revision. +Currently, `System.Net.Quic` depends on [**msquic@26cff1a8de7890cf7ff77709ee14b51bc84e330e**](https://github.com/microsoft/msquic/commit/26cff1a8de7890cf7ff77709ee14b51bc84e330e) revision. ## Usage diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/MsQuicNativeMethods.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/MsQuicNativeMethods.cs index cebcdaf46f07a..2321364ce4649 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/MsQuicNativeMethods.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/MsQuicNativeMethods.cs @@ -544,6 +544,12 @@ internal struct StreamEventDataSendShutdownComplete internal byte Graceful; } + [StructLayout(LayoutKind.Sequential)] + internal struct StreamEventDataShutdownComplete + { + internal byte ConnectionShutdown; + } + [StructLayout(LayoutKind.Explicit)] internal struct StreamEventDataUnion { @@ -563,6 +569,9 @@ internal struct StreamEventDataUnion [FieldOffset(0)] internal StreamEventDataSendShutdownComplete SendShutdownComplete; + [FieldOffset(0)] + internal StreamEventDataShutdownComplete ShutdownComplete; + // TODO: missing IDEAL_SEND_BUFFER_SIZE } diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs index 6d6431fc728af..cf03d2309674a 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs @@ -40,7 +40,7 @@ internal sealed class MsQuicConnection : QuicConnectionProvider private X509RevocationMode _revocationMode = X509RevocationMode.Offline; private RemoteCertificateValidationCallback? _remoteCertificateValidationCallback; - private sealed class State + internal sealed class State { public SafeMsQuicConnectionHandle Handle = null!; // set inside of MsQuicConnection ctor. @@ -87,6 +87,11 @@ public MsQuicConnection(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, Saf _stateHandle.Free(); throw; } + + if (NetEventSource.Log.IsEnabled()) + { + NetEventSource.Info(_state, $"[Connection#{_state.GetHashCode()}] inbound connection created"); + } } // constructor for outbound connections @@ -121,6 +126,11 @@ public MsQuicConnection(QuicClientConnectionOptions options) _stateHandle.Free(); throw; } + + if (NetEventSource.Log.IsEnabled()) + { + NetEventSource.Info(_state, $"[Connection#{_state.GetHashCode()}] outbound connection created"); + } } internal override IPEndPoint? LocalEndPoint => _localEndPoint; @@ -188,7 +198,7 @@ private static uint HandleEventShutdownComplete(State state, ref ConnectionEvent private static uint HandleEventNewStream(State state, ref ConnectionEvent connectionEvent) { var streamHandle = new SafeMsQuicStreamHandle(connectionEvent.Data.PeerStreamStarted.Stream); - var stream = new MsQuicStream(streamHandle, connectionEvent.Data.PeerStreamStarted.Flags); + var stream = new MsQuicStream(state, streamHandle, connectionEvent.Data.PeerStreamStarted.Flags); state.AcceptQueue.Writer.TryWrite(stream); return MsQuicStatusCodes.Success; @@ -284,18 +294,18 @@ private static uint HandleEventPeerCertificateReceived(State state, ref Connecti { bool success = connection._remoteCertificateValidationCallback(connection, certificate, chain, sslPolicyErrors); if (!success && NetEventSource.Log.IsEnabled()) - NetEventSource.Error(state.Connection, "Remote certificate rejected by verification callback"); + NetEventSource.Error(state, $"[Connection#{state.GetHashCode()}] remote certificate rejected by verification callback"); return success ? MsQuicStatusCodes.Success : MsQuicStatusCodes.HandshakeFailure; } if (NetEventSource.Log.IsEnabled()) - NetEventSource.Info(state.Connection, $"Certificate validation for '${certificate?.Subject}' finished with ${sslPolicyErrors}"); + NetEventSource.Info(state, $"[Connection#{state.GetHashCode()}] certificate validation for '${certificate?.Subject}' finished with ${sslPolicyErrors}"); return (sslPolicyErrors == SslPolicyErrors.None) ? MsQuicStatusCodes.Success : MsQuicStatusCodes.HandshakeFailure; } catch (Exception ex) { - if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(state.Connection, $"Certificate validation failed ${ex.Message}"); + if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(state, $"[Connection#{state.GetHashCode()}] certificate validation failed ${ex.Message}"); } return MsQuicStatusCodes.InternalError; @@ -313,11 +323,7 @@ internal override async ValueTask AcceptStreamAsync(Cancella } catch (ChannelClosedException) { - throw _state.AbortErrorCode switch - { - -1 => new QuicOperationAbortedException(), // Shutdown initiated by us. - long err => new QuicConnectionAbortedException(err) // Shutdown initiated by peer. - }; + throw ThrowHelper.GetConnectionAbortedException(_state.AbortErrorCode); } return stream; @@ -326,13 +332,13 @@ internal override async ValueTask AcceptStreamAsync(Cancella internal override QuicStreamProvider OpenUnidirectionalStream() { ThrowIfDisposed(); - return new MsQuicStream(_state.Handle, QUIC_STREAM_OPEN_FLAGS.UNIDIRECTIONAL); + return new MsQuicStream(_state, QUIC_STREAM_OPEN_FLAGS.UNIDIRECTIONAL); } internal override QuicStreamProvider OpenBidirectionalStream() { ThrowIfDisposed(); - return new MsQuicStream(_state.Handle, QUIC_STREAM_OPEN_FLAGS.NONE); + return new MsQuicStream(_state, QUIC_STREAM_OPEN_FLAGS.NONE); } internal override long GetRemoteAvailableUnidirectionalStreamCount() @@ -430,6 +436,12 @@ internal void SetNegotiatedAlpn(IntPtr alpn, int alpnLength) ref ConnectionEvent connectionEvent) { var state = (State)GCHandle.FromIntPtr(context).Target!; + + if (NetEventSource.Log.IsEnabled()) + { + NetEventSource.Info(state, $"[Connection#{state.GetHashCode()}] received event {connectionEvent.Type}"); + } + try { switch (connectionEvent.Type) diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicStream.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicStream.cs index f1bfb2efdc86d..6102235f7e1fe 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicStream.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicStream.cs @@ -38,6 +38,7 @@ internal sealed class MsQuicStream : QuicStreamProvider private sealed class State { public SafeMsQuicStreamHandle Handle = null!; // set in ctor. + public MsQuicConnection.State ConnectionState = null!; // set in ctor. public ReadState ReadState; public long ReadErrorCode = -1; @@ -71,9 +72,10 @@ private sealed class State } // inbound. - internal MsQuicStream(SafeMsQuicStreamHandle streamHandle, QUIC_STREAM_OPEN_FLAGS flags) + internal MsQuicStream(MsQuicConnection.State connectionState, SafeMsQuicStreamHandle streamHandle, QUIC_STREAM_OPEN_FLAGS flags) { _state.Handle = streamHandle; + _state.ConnectionState = connectionState; _canRead = true; _canWrite = !flags.HasFlag(QUIC_STREAM_OPEN_FLAGS.UNIDIRECTIONAL); _started = true; @@ -91,13 +93,22 @@ internal MsQuicStream(SafeMsQuicStreamHandle streamHandle, QUIC_STREAM_OPEN_FLAG _stateHandle.Free(); throw; } + + if (NetEventSource.Log.IsEnabled()) + { + NetEventSource.Info( + _state, + $"[Stream#{_state.GetHashCode()}] inbound {(_canWrite ? "bi" : "uni")}directional stream created " + + $"in Connection#{_state.ConnectionState.GetHashCode()}."); + } } // outbound. - internal MsQuicStream(SafeMsQuicConnectionHandle connection, QUIC_STREAM_OPEN_FLAGS flags) + internal MsQuicStream(MsQuicConnection.State connectionState, QUIC_STREAM_OPEN_FLAGS flags) { - Debug.Assert(connection != null); + Debug.Assert(connectionState.Handle != null); + _state.ConnectionState = connectionState; _canRead = !flags.HasFlag(QUIC_STREAM_OPEN_FLAGS.UNIDIRECTIONAL); _canWrite = true; @@ -105,7 +116,7 @@ internal MsQuicStream(SafeMsQuicConnectionHandle connection, QUIC_STREAM_OPEN_FL try { uint status = MsQuicApi.Api.StreamOpenDelegate( - connection, + connectionState.Handle, flags, s_streamDelegate, GCHandle.ToIntPtr(_stateHandle), @@ -122,6 +133,14 @@ internal MsQuicStream(SafeMsQuicConnectionHandle connection, QUIC_STREAM_OPEN_FL _stateHandle.Free(); throw; } + + if (NetEventSource.Log.IsEnabled()) + { + NetEventSource.Info( + _state, + $"[Stream#{_state.GetHashCode()}] outbound {(_canRead ? "bi" : "uni")}directional stream created " + + $"in Connection#{_state.ConnectionState.GetHashCode()}."); + } } internal override bool CanRead => _canRead; @@ -203,6 +222,10 @@ private async ValueTask HandleWriteStartState(Can { throw new OperationCanceledException(SR.net_quic_sending_aborted); } + else if (_state.SendState == SendState.ConnectionClosed) + { + throw GetConnectionAbortedException(_state); + } } CancellationTokenRegistration registration = cancellationToken.UnsafeRegister(static (s, token) => @@ -268,7 +291,7 @@ internal override async ValueTask ReadAsync(Memory destination, Cance if (NetEventSource.Log.IsEnabled()) { - NetEventSource.Info(this, $"[{GetHashCode()}] reading into Memory of '{destination.Length}' bytes."); + NetEventSource.Info(_state, $"[Stream#{_state.GetHashCode()}] reading into Memory of '{destination.Length}' bytes."); } lock (_state) @@ -279,11 +302,11 @@ internal override async ValueTask ReadAsync(Memory destination, Cance } else if (_state.ReadState == ReadState.Aborted) { - throw _state.ReadErrorCode switch - { - -1 => new QuicOperationAbortedException(), - long err => new QuicStreamAbortedException(err) - }; + throw ThrowHelper.GetStreamAbortedException(_state.ReadErrorCode); + } + else if (_state.ReadState == ReadState.ConnectionClosed) + { + throw GetConnectionAbortedException(_state); } } @@ -396,6 +419,14 @@ internal override async ValueTask ShutdownWriteCompleted(CancellationToken cance { ThrowIfDisposed(); + lock (_state) + { + if (_state.ShutdownWriteState == ShutdownWriteState.ConnectionClosed) + { + throw GetConnectionAbortedException(_state); + } + } + // TODO do anything to stop writes? using CancellationTokenRegistration registration = cancellationToken.UnsafeRegister(static (s, token) => { @@ -424,6 +455,14 @@ internal override async ValueTask ShutdownCompleted(CancellationToken cancellati { ThrowIfDisposed(); + lock (_state) + { + if (_state.ShutdownState == ShutdownState.ConnectionClosed) + { + throw GetConnectionAbortedException(_state); + } + } + // TODO do anything to stop writes? using CancellationTokenRegistration registration = cancellationToken.UnsafeRegister(static (s, token) => { @@ -516,6 +555,11 @@ private void Dispose(bool disposing) Marshal.FreeHGlobal(_state.SendQuicBuffers); if (_stateHandle.IsAllocated) _stateHandle.Free(); CleanupSendState(_state); + + if (NetEventSource.Log.IsEnabled()) + { + NetEventSource.Info(_state, $"[Stream#{_state.GetHashCode()}] disposed"); + } } private void EnableReceive() @@ -536,7 +580,7 @@ private static uint HandleEvent(State state, ref StreamEvent evt) { if (NetEventSource.Log.IsEnabled()) { - NetEventSource.Info(state, $"[{state.GetHashCode()}] received event {evt.Type}"); + NetEventSource.Info(state, $"[Stream#{state.GetHashCode()}] received event {evt.Type}"); } try @@ -570,7 +614,7 @@ private static uint HandleEvent(State state, ref StreamEvent evt) return HandleEventSendShutdownComplete(state, ref evt); // Shutdown for both sending and receiving is completed. case QUIC_STREAM_EVENT_TYPE.SHUTDOWN_COMPLETE: - return HandleEventShutdownComplete(state); + return HandleEventShutdownComplete(state, ref evt); default: return MsQuicStatusCodes.Success; } @@ -596,7 +640,10 @@ private static unsafe uint HandleEventRecv(State state, ref StreamEvent evt) { shouldComplete = true; } - state.ReadState = ReadState.IndividualReadComplete; + if (state.ReadState != ReadState.ConnectionClosed) + { + state.ReadState = ReadState.IndividualReadComplete; + } } if (shouldComplete) @@ -669,8 +716,15 @@ private static uint HandleEventSendShutdownComplete(State state, ref StreamEvent return MsQuicStatusCodes.Success; } - private static uint HandleEventShutdownComplete(State state) + private static uint HandleEventShutdownComplete(State state, ref StreamEvent evt) { + StreamEventDataShutdownComplete shutdownCompleteEvent = evt.Data.ShutdownComplete; + + if (shutdownCompleteEvent.ConnectionShutdown != 0) + { + return HandleEventConnectionClose(state); + } + bool shouldReadComplete = false; bool shouldShutdownWriteComplete = false; bool shouldShutdownComplete = false; @@ -678,14 +732,17 @@ private static uint HandleEventShutdownComplete(State state) lock (state) { // This event won't occur within the middle of a receive. - if (NetEventSource.Log.IsEnabled()) NetEventSource.Info("Completing resettable event source."); + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(state, $"[Stream#{state.GetHashCode()}] completing resettable event source."); if (state.ReadState == ReadState.None) { shouldReadComplete = true; } - state.ReadState = ReadState.ReadsCompleted; + if (state.ReadState != ReadState.ConnectionClosed) + { + state.ReadState = ReadState.ReadsCompleted; + } if (state.ShutdownWriteState == ShutdownWriteState.None) { @@ -747,14 +804,17 @@ private static uint HandleEventPeerSendShutdown(State state) lock (state) { // This event won't occur within the middle of a receive. - if (NetEventSource.Log.IsEnabled()) NetEventSource.Info("Completing resettable event source."); + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(state, $"[Stream#{state.GetHashCode()}] completing resettable event source."); if (state.ReadState == ReadState.None) { shouldComplete = true; } - state.ReadState = ReadState.ReadsCompleted; + if (state.ReadState != ReadState.ConnectionClosed) + { + state.ReadState = ReadState.ReadsCompleted; + } } if (shouldComplete) @@ -1013,6 +1073,77 @@ private void ThrowIfDisposed() } } + private static uint HandleEventConnectionClose(State state) + { + long errorCode = state.ConnectionState.AbortErrorCode; + if (NetEventSource.Log.IsEnabled()) + { + NetEventSource.Info(state, $"[Stream#{state.GetHashCode()}] handling Connection#{state.ConnectionState.GetHashCode()} close" + + (errorCode != -1 ? $" with code {errorCode}" : "")); + } + + bool shouldCompleteRead = false; + bool shouldCompleteSend = false; + bool shouldCompleteShutdownWrite = false; + bool shouldCompleteShutdown = false; + + lock (state) + { + if (state.ReadState == ReadState.None) + { + shouldCompleteRead = true; + } + state.ReadState = ReadState.ConnectionClosed; + + if (state.SendState == SendState.None || state.SendState == SendState.Pending) + { + shouldCompleteSend = true; + } + state.SendState = SendState.ConnectionClosed; + + if (state.ShutdownWriteState == ShutdownWriteState.None) + { + shouldCompleteShutdownWrite = true; + } + state.ShutdownWriteState = ShutdownWriteState.ConnectionClosed; + + if (state.ShutdownState == ShutdownState.None) + { + shouldCompleteShutdown = true; + } + state.ShutdownState = ShutdownState.ConnectionClosed; + } + + if (shouldCompleteRead) + { + state.ReceiveResettableCompletionSource.CompleteException( + ExceptionDispatchInfo.SetCurrentStackTrace(GetConnectionAbortedException(state))); + } + + if (shouldCompleteSend) + { + state.SendResettableCompletionSource.CompleteException( + ExceptionDispatchInfo.SetCurrentStackTrace(GetConnectionAbortedException(state))); + } + + if (shouldCompleteShutdownWrite) + { + state.ShutdownWriteCompletionSource.SetException( + ExceptionDispatchInfo.SetCurrentStackTrace(GetConnectionAbortedException(state))); + } + + if (shouldCompleteShutdown) + { + state.ShutdownCompletionSource.SetException( + ExceptionDispatchInfo.SetCurrentStackTrace(GetConnectionAbortedException(state))); + } + + return MsQuicStatusCodes.Success; + } + + private static Exception GetConnectionAbortedException(State state) => + ThrowHelper.GetConnectionAbortedException(state.ConnectionState.AbortErrorCode); + private enum ReadState { /// @@ -1033,21 +1164,28 @@ private enum ReadState /// /// User has aborted the stream, either via a cancellation token on ReadAsync(), or via AbortRead(). /// - Aborted + Aborted, + + /// + /// Connection was closed, either by user or by the peer. + /// + ConnectionClosed } private enum ShutdownWriteState { None, Canceled, - Finished + Finished, + ConnectionClosed } private enum ShutdownState { None, Canceled, - Finished + Finished, + ConnectionClosed } private enum SendState @@ -1055,7 +1193,8 @@ private enum SendState None, Pending, Aborted, - Finished + Finished, + ConnectionClosed } } } diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/ThrowHelper.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/ThrowHelper.cs new file mode 100644 index 0000000000000..bc8379d62587d --- /dev/null +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/ThrowHelper.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Net.Quic.Implementations.MsQuic +{ + internal static class ThrowHelper + { + internal static Exception GetConnectionAbortedException(long errorCode) + { + return errorCode switch + { + -1 => new QuicOperationAbortedException(), // Shutdown initiated by us. + long err => new QuicConnectionAbortedException(err) // Shutdown initiated by peer. + }; + } + + internal static Exception GetStreamAbortedException(long errorCode) + { + return errorCode switch + { + -1 => new QuicOperationAbortedException(), // Shutdown initiated by us. + long err => new QuicStreamAbortedException(err) // Shutdown initiated by peer. + }; + } + } +} \ No newline at end of file diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs index 446daf8021d8b..dd4a3fd9265c6 100644 --- a/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs @@ -78,7 +78,6 @@ public async Task SetListenerTimeoutWorksWithSmallTimeout() await Assert.ThrowsAsync(async () => await serverConnection.AcceptStreamAsync().AsTask().WaitAsync(TimeSpan.FromSeconds(100))); } - [ActiveIssue("https://github.com/dotnet/runtime/issues/52047")] [Theory] [MemberData(nameof(WriteData))] public async Task WriteTests(int[][] writes, WriteType writeType) @@ -374,5 +373,60 @@ async Task GetStreamIdWithoutStartWorks() GC.Collect(); } + + [Fact] + public async Task Read_ConnectionAbortedByPeer_Throws() + { + const int ExpectedErrorCode = 1234; + + await Task.Run(async () => + { + using QuicListener listener = CreateQuicListener(); + ValueTask serverConnectionTask = listener.AcceptConnectionAsync(); + + using QuicConnection clientConnection = CreateQuicConnection(listener.ListenEndPoint); + await clientConnection.ConnectAsync(); + + using QuicConnection serverConnection = await serverConnectionTask; + + await using QuicStream clientStream = clientConnection.OpenBidirectionalStream(); + await clientStream.WriteAsync(new byte[1]); + + await using QuicStream serverStream = await serverConnection.AcceptStreamAsync(); + await serverStream.ReadAsync(new byte[1]); + + await clientConnection.CloseAsync(ExpectedErrorCode); + + byte[] buffer = new byte[100]; + QuicConnectionAbortedException ex = await Assert.ThrowsAsync(() => serverStream.ReadAsync(buffer).AsTask()); + Assert.Equal(ExpectedErrorCode, ex.ErrorCode); + }).WaitAsync(TimeSpan.FromSeconds(5)); + } + + [Fact] + public async Task Read_ConnectionAbortedByUser_Throws() + { + await Task.Run(async () => + { + using QuicListener listener = CreateQuicListener(); + ValueTask serverConnectionTask = listener.AcceptConnectionAsync(); + + using QuicConnection clientConnection = CreateQuicConnection(listener.ListenEndPoint); + await clientConnection.ConnectAsync(); + + using QuicConnection serverConnection = await serverConnectionTask; + + await using QuicStream clientStream = clientConnection.OpenBidirectionalStream(); + await clientStream.WriteAsync(new byte[1]); + + await using QuicStream serverStream = await serverConnection.AcceptStreamAsync(); + await serverStream.ReadAsync(new byte[1]); + + await serverConnection.CloseAsync(0); + + byte[] buffer = new byte[100]; + await Assert.ThrowsAsync(() => serverStream.ReadAsync(buffer).AsTask()); + }).WaitAsync(TimeSpan.FromSeconds(5)); + } } } diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamTests.cs index b08f93f94486e..72243c3bdb723 100644 --- a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamTests.cs +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamTests.cs @@ -434,36 +434,6 @@ public async Task Read_StreamAborted_Throws() Assert.Equal(ExpectedErrorCode, ex.ErrorCode); }).WaitAsync(TimeSpan.FromSeconds(15)); } - - [ActiveIssue("https://github.com/dotnet/runtime/issues/32050")] - [Fact] - public async Task Read_ConnectionAborted_Throws() - { - const int ExpectedErrorCode = 1234; - - await Task.Run(async () => - { - using QuicListener listener = CreateQuicListener(); - ValueTask serverConnectionTask = listener.AcceptConnectionAsync(); - - using QuicConnection clientConnection = CreateQuicConnection(listener.ListenEndPoint); - await clientConnection.ConnectAsync(); - - using QuicConnection serverConnection = await serverConnectionTask; - - await using QuicStream clientStream = clientConnection.OpenBidirectionalStream(); - await clientStream.WriteAsync(new byte[1]); - - await using QuicStream serverStream = await serverConnection.AcceptStreamAsync(); - await serverStream.ReadAsync(new byte[1]); - - await clientConnection.CloseAsync(ExpectedErrorCode); - - byte[] buffer = new byte[100]; - QuicConnectionAbortedException ex = await Assert.ThrowsAsync(() => serverStream.ReadAsync(buffer).AsTask()); - Assert.Equal(ExpectedErrorCode, ex.ErrorCode); - }).WaitAsync(TimeSpan.FromSeconds(5)); - } } public sealed class QuicStreamTests_MockProvider : QuicStreamTests { }