From 3919515a3b9120f9bf623038cce38f5643776b54 Mon Sep 17 00:00:00 2001 From: Radek Zikmund Date: Wed, 30 Mar 2022 11:44:28 +0200 Subject: [PATCH 01/10] Throw on concurrent operations --- src/libraries/System.Net.Quic/src/Resources/Strings.resx | 3 +++ .../System/Net/Quic/Implementations/MsQuic/MsQuicStream.cs | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Net.Quic/src/Resources/Strings.resx b/src/libraries/System.Net.Quic/src/Resources/Strings.resx index f1e227448e030a..04bfbf33de825f 100644 --- a/src/libraries/System.Net.Quic/src/Resources/Strings.resx +++ b/src/libraries/System.Net.Quic/src/Resources/Strings.resx @@ -165,5 +165,8 @@ The application protocol list is invalid. + + This method may not be called when another {0} operation is pending. + 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 1a1df28b699a88..b164911e66474a 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 @@ -305,6 +305,11 @@ internal override async ValueTask WriteAsync(ReadOnlyMemory buffer, bool e private CancellationTokenRegistration HandleWriteStartState(bool emptyBuffer, CancellationToken cancellationToken) { + if (_state.SendState == SendState.Pending) + { + throw new InvalidOperationException(SR.Format(SR.net_io_invalidnestedcall, "write")); + } + if (_state.SendState == SendState.Closed) { throw new InvalidOperationException(SR.net_quic_writing_notallowed); @@ -507,7 +512,7 @@ internal override ValueTask ReadAsync(Memory destination, Cancellatio switch (initialReadState) { case ReadState.PendingRead: - ex = new InvalidOperationException("Only one read is supported at a time."); + ex = new InvalidOperationException(SR.Format(SR.net_io_invalidnestedcall, "read")); break; case ReadState.Aborted: ex = preCanceled ? new OperationCanceledException(cancellationToken) : From 1bc1e900169e98e2736af06cc48ab399667387a3 Mon Sep 17 00:00:00 2001 From: Radek Zikmund Date: Wed, 30 Mar 2022 11:45:00 +0200 Subject: [PATCH 02/10] Remove redundant test --- ...icStreamConnectedStreamConformanceTests.cs | 1 + .../tests/FunctionalTests/QuicStreamTests.cs | 26 +------------------ 2 files changed, 2 insertions(+), 25 deletions(-) diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs index 9856f5cd77fcc9..7438842cc9c582 100644 --- a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs @@ -26,6 +26,7 @@ public sealed class MockQuicStreamConformanceTests : QuicStreamConformanceTests public sealed class MsQuicQuicStreamConformanceTests : QuicStreamConformanceTests { protected override QuicImplementationProvider Provider => QuicImplementationProviders.MsQuic; + protected override Type UnsupportedConcurrentExceptionType => typeof(InvalidOperationException); protected override bool UsableAfterCanceledReads => false; protected override bool BlocksOnZeroByteReads => true; protected override bool CanTimeout => true; diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamTests.cs index d2551ab89ac8ff..2b8477073478b2 100644 --- a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamTests.cs +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamTests.cs @@ -182,7 +182,7 @@ static async Task MakeStreams(QuicConnection clientConnection, QuicConnection se QuicStream clientStream = clientConnection.OpenBidirectionalStream(); ValueTask writeTask = clientStream.WriteAsync(Encoding.UTF8.GetBytes("PING"), endStream: true); ValueTask acceptTask = serverConnection.AcceptStreamAsync(); - await new Task[] { writeTask.AsTask(), acceptTask.AsTask()}.WhenAllOrAnyFailed(PassingTestTimeoutMilliseconds); + await new Task[] { writeTask.AsTask(), acceptTask.AsTask() }.WhenAllOrAnyFailed(PassingTestTimeoutMilliseconds); QuicStream serverStream = acceptTask.Result; await serverStream.ReadAsync(buffer); } @@ -532,30 +532,6 @@ public async Task ReadOutstanding_ReadAborted_Throws() } } - [Fact] - public async Task Read_ConcurrentReads_Throws() - { - using SemaphoreSlim sem = new SemaphoreSlim(0); - - await RunBidirectionalClientServer( - async clientStream => - { - await sem.WaitAsync(); - }, - async serverStream => - { - ValueTask readTask = serverStream.ReadAsync(new byte[1]); - Assert.False(readTask.IsCompleted); - - await Assert.ThrowsAsync(async () => await serverStream.ReadAsync(new byte[1])); - - sem.Release(); - - int res = await readTask; - Assert.Equal(0, res); - }); - } - [Fact] public async Task WriteAbortedWithoutWriting_ReadThrows() { From ad6cff8d13c6f62bee5fe0903c3f6680ffde45f9 Mon Sep 17 00:00:00 2001 From: Radek Zikmund Date: Wed, 30 Mar 2022 13:51:30 +0200 Subject: [PATCH 03/10] Move _state.SendState access under a lock --- .../Implementations/MsQuic/MsQuicStream.cs | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) 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 b164911e66474a..3f8412e0804d00 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 @@ -227,7 +227,7 @@ internal override int WriteTimeout get { ThrowIfDisposed(); - return _writeTimeout; + return _writeTimeout; } set { @@ -305,11 +305,6 @@ internal override async ValueTask WriteAsync(ReadOnlyMemory buffer, bool e private CancellationTokenRegistration HandleWriteStartState(bool emptyBuffer, CancellationToken cancellationToken) { - if (_state.SendState == SendState.Pending) - { - throw new InvalidOperationException(SR.Format(SR.net_io_invalidnestedcall, "write")); - } - if (_state.SendState == SendState.Closed) { throw new InvalidOperationException(SR.net_quic_writing_notallowed); @@ -377,6 +372,11 @@ private CancellationTokenRegistration HandleWriteStartState(bool emptyBuffer, Ca throw GetConnectionAbortedException(_state); } + if (_state.SendState == SendState.Pending || _state.SendState == SendState.Finished) + { + throw new InvalidOperationException(SR.Format(SR.net_io_invalidnestedcall, "write")); + } + // Change the state in the same lock where we check for final states to prevent coming back from Aborted/ConnectionClosed. Debug.Assert(_state.SendState != SendState.Pending); _state.SendState = emptyBuffer ? SendState.Finished : SendState.Pending; @@ -515,7 +515,7 @@ internal override ValueTask ReadAsync(Memory destination, Cancellatio ex = new InvalidOperationException(SR.Format(SR.net_io_invalidnestedcall, "read")); break; case ReadState.Aborted: - ex = preCanceled ? new OperationCanceledException(cancellationToken) : + ex = preCanceled ? new OperationCanceledException(cancellationToken) : ThrowHelper.GetStreamAbortedException(abortError); break; case ReadState.ConnectionClosed: @@ -823,7 +823,8 @@ private void Dispose(bool disposing) { // Handle race condition when stream can be closed handling SHUTDOWN_COMPLETE. StartShutdown(QUIC_STREAM_SHUTDOWN_FLAGS.GRACEFUL, errorCode: 0); - } catch (ObjectDisposedException) { }; + } + catch (ObjectDisposedException) { }; } if (abortRead) @@ -831,7 +832,8 @@ private void Dispose(bool disposing) try { StartShutdown(QUIC_STREAM_SHUTDOWN_FLAGS.ABORT_RECEIVE, 0xffffffff); - } catch (ObjectDisposedException) { }; + } + catch (ObjectDisposedException) { }; } if (completeRead) From d59de1d85b07cf3718c7fe5b204e244a3f5fed2a Mon Sep 17 00:00:00 2001 From: Radek Zikmund Date: Thu, 7 Apr 2022 17:23:19 +0200 Subject: [PATCH 04/10] Fix data race on ReceiveResettableCompletionSource --- .../Quic/Implementations/MsQuic/MsQuicStream.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) 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 5e33c780ee695d..30b64840e09598 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 @@ -927,7 +927,7 @@ private static uint HandleEvent(State state, ref StreamEvent evt) return HandleEventStartComplete(state, ref evt); // Received data on the stream case QUIC_STREAM_EVENT_TYPE.RECEIVE: - return HandleEventRecv(state, ref evt); + return HandleEventReceive(state, ref evt); // Send has completed. // Contains a canceled bool to indicate if the send was canceled. case QUIC_STREAM_EVENT_TYPE.SEND_COMPLETE: @@ -966,7 +966,7 @@ private static uint HandleEvent(State state, ref StreamEvent evt) } } - private static unsafe uint HandleEventRecv(State state, ref StreamEvent evt) + private static unsafe uint HandleEventReceive(State state, ref StreamEvent evt) { ref StreamEventDataReceive receiveEvent = ref evt.Data.Receive; @@ -1051,18 +1051,19 @@ private static unsafe uint HandleEventRecv(State state, ref StreamEvent evt) break; default: - Debug.Assert(state.ReadState is ReadState.Aborted or ReadState.ConnectionClosed, $"Unexpected {nameof(ReadState)} '{state.ReadState}' in {nameof(HandleEventRecv)}."); + Debug.Assert(state.ReadState is ReadState.Aborted or ReadState.ConnectionClosed, $"Unexpected {nameof(ReadState)} '{state.ReadState}' in {nameof(HandleEventReceive)}."); // There was a race between a user aborting the read stream and the callback being ran. // This will eat any received data. return MsQuicStatusCodes.Success; } - } - // We're completing a pending read. - if (shouldComplete) - { - state.ReceiveResettableCompletionSource.Complete(readLength); + // We're completing a pending read. This statement must be still inside a lock, otherwise another thread + // could see ReadState.None and access the still incomplete completion source + if (shouldComplete) + { + state.ReceiveResettableCompletionSource.Complete(readLength); + } } // Returning Success when the entire buffer hasn't been consumed will cause MsQuic to disable further receive events until EnableReceive() is called. From b1eb32883c666535d6e74734501ef8a53a291ff5 Mon Sep 17 00:00:00 2001 From: Radek Zikmund Date: Mon, 11 Apr 2022 10:47:49 +0200 Subject: [PATCH 05/10] Remove unnecessary override --- .../FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs index 97ce233d71c651..5df77a4ced54af 100644 --- a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs @@ -27,7 +27,6 @@ public sealed class MockQuicStreamConformanceTests : QuicStreamConformanceTests public sealed class MsQuicQuicStreamConformanceTests : QuicStreamConformanceTests { protected override QuicImplementationProvider Provider => QuicImplementationProviders.MsQuic; - protected override Type UnsupportedConcurrentExceptionType => typeof(InvalidOperationException); protected override bool UsableAfterCanceledReads => false; protected override bool BlocksOnZeroByteReads => true; protected override bool CanTimeout => true; From de3066416c48374363f3120a4d2440ceaf649f36 Mon Sep 17 00:00:00 2001 From: Radek Zikmund Date: Fri, 22 Apr 2022 15:10:59 +0200 Subject: [PATCH 06/10] Move task completion outside of the lock --- .../Quic/Implementations/MsQuic/MsQuicStream.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) 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 59e21649057919..c8d61e3af69dc1 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 @@ -1035,7 +1035,7 @@ private static unsafe uint HandleEventReceive(State state, ref StreamEvent evt) state.ReceiveCancellationRegistration.Unregister(); shouldComplete = true; state.Stream = null; - state.ReadState = ReadState.None; + // state.ReadState will be set to None later once the ReceiveResettableCompletionSource is completed to avoid data race on parallel reads. readLength = CopyMsQuicBuffersToUserBuffer(new ReadOnlySpan(receiveEvent.Buffers, (int)receiveEvent.BufferCount), state.ReceiveUserBuffer.Span); @@ -1057,11 +1057,15 @@ private static unsafe uint HandleEventReceive(State state, ref StreamEvent evt) return MsQuicStatusCodes.Success; } - // We're completing a pending read. This statement must be still inside a lock, otherwise another thread - // could see ReadState.None and access the still incomplete completion source - if (shouldComplete) + } + + if (shouldComplete) + { + state.ReceiveResettableCompletionSource.Complete(readLength); + + lock (state) { - state.ReceiveResettableCompletionSource.Complete(readLength); + state.ReadState = ReadState.None; } } From 426ecdbf9ccf1358a55c4916968493bf0fb6a02e Mon Sep 17 00:00:00 2001 From: Radek Zikmund Date: Mon, 25 Apr 2022 10:39:29 +0200 Subject: [PATCH 07/10] Fix failing tests --- .../Implementations/MsQuic/MsQuicStream.cs | 43 +++++++++++++------ 1 file changed, 30 insertions(+), 13 deletions(-) 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 c8d61e3af69dc1..da349d4e23b295 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 @@ -421,7 +421,7 @@ private void CleanupWriteFailedState() } } - internal override ValueTask ReadAsync(Memory destination, CancellationToken cancellationToken = default) + internal override async ValueTask ReadAsync(Memory destination, CancellationToken cancellationToken = default) { ThrowIfDisposed(); @@ -459,7 +459,7 @@ internal override ValueTask ReadAsync(Memory destination, Cancellatio // Success scenario: EOS already reached, completing synchronously. No transition (final state) if (initialReadState == ReadState.ReadsCompleted) { - return new ValueTask(0); + return 0; } // Success scenario: no data available yet, will return a task to wait on. Transition None->PendingRead @@ -493,8 +493,6 @@ internal override ValueTask ReadAsync(Memory destination, Cancellatio { _state.ReceiveCancellationRegistration = default; } - - return _state.ReceiveResettableCompletionSource.GetValueTask(); } // Success scenario: data already available, completing synchronously. @@ -518,6 +516,23 @@ internal override ValueTask ReadAsync(Memory destination, Cancellatio } } + if (initialReadState == ReadState.None) + { + // wait for the read to finish + bytesRead = await _state.ReceiveResettableCompletionSource.GetValueTask().ConfigureAwait(false); + + // Reset the read state + lock (_state) + { + if (_state.ReadState == ReadState.PendingReadFinished) + { + _state.ReadState = ReadState.None; + } + } + + return bytesRead; + } + // methods below need to be called outside of the lock if (bytesRead > -1) { @@ -528,7 +543,7 @@ internal override ValueTask ReadAsync(Memory destination, Cancellatio EnableReceive(); } - return new ValueTask(bytesRead); + return bytesRead; } // All success scenarios returned at this point. Failure scenarios below: @@ -551,7 +566,7 @@ internal override ValueTask ReadAsync(Memory destination, Cancellatio break; } - return ValueTask.FromException(ExceptionDispatchInfo.SetCurrentStackTrace(ex!)); + throw ex; } /// The number of bytes copied. @@ -982,6 +997,7 @@ private static unsafe uint HandleEventReceive(State state, ref StreamEvent evt) switch (state.ReadState) { case ReadState.None: + case ReadState.PendingReadFinished: // ReadAsync() hasn't been called yet. Stash the buffer so the next ReadAsync call completes synchronously. // We are overwriting state.ReceiveQuicBuffers here even if we only partially consumed them @@ -1035,7 +1051,8 @@ private static unsafe uint HandleEventReceive(State state, ref StreamEvent evt) state.ReceiveCancellationRegistration.Unregister(); shouldComplete = true; state.Stream = null; - // state.ReadState will be set to None later once the ReceiveResettableCompletionSource is completed to avoid data race on parallel reads. + state.ReadState = ReadState.PendingReadFinished; + // state.ReadState will be set to None later once the ReceiveResettableCompletionSource is awaited. readLength = CopyMsQuicBuffersToUserBuffer(new ReadOnlySpan(receiveEvent.Buffers, (int)receiveEvent.BufferCount), state.ReceiveUserBuffer.Span); @@ -1056,17 +1073,12 @@ private static unsafe uint HandleEventReceive(State state, ref StreamEvent evt) // This will eat any received data. return MsQuicStatusCodes.Success; } - } if (shouldComplete) { state.ReceiveResettableCompletionSource.Complete(readLength); - - lock (state) - { - state.ReadState = ReadState.None; - } + // _state.ReadState will be reset to None on the reading thread. } // Returning Success when the entire buffer hasn't been consumed will cause MsQuic to disable further receive events until EnableReceive() is called. @@ -1668,6 +1680,11 @@ private enum ReadState /// PendingRead, + /// + /// Read was completed from the MsQuic callback. + /// + PendingReadFinished, + // following states are final: /// From 745443487da4a376d31ea9dc82f52c64fe612262 Mon Sep 17 00:00:00 2001 From: Radek Zikmund Date: Tue, 26 Apr 2022 20:21:28 +0200 Subject: [PATCH 08/10] Update comments --- .../Net/Quic/Implementations/MsQuic/MsQuicStream.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) 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 a1e763dfc5fbf5..fd56cd93bb68f9 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 @@ -522,7 +522,7 @@ internal override async ValueTask ReadAsync(Memory destination, Cance if (initialReadState == ReadState.None) { - // wait for the read to finish + // wait for the incoming data to finish the read. bytesRead = await _state.ReceiveResettableCompletionSource.GetValueTask().ConfigureAwait(false); // Reset the read state @@ -1000,9 +1000,12 @@ private static unsafe uint HandleEventReceive(State state, ref StreamEvent evt) { switch (state.ReadState) { + // ReadAsync() hasn't been called yet. case ReadState.None: + // A pending read has just been finished, and this is a second event in a row (before reading thread + // managed to clear the state) case ReadState.PendingReadFinished: - // ReadAsync() hasn't been called yet. Stash the buffer so the next ReadAsync call completes synchronously. + // Stash the buffer so the next ReadAsync call completes synchronously. // We are overwriting state.ReceiveQuicBuffers here even if we only partially consumed them // and it is intended, because unconsumed data will arrive again from the point we've stopped. @@ -1656,10 +1659,12 @@ private static bool CleanupReadStateAndCheckPending(State state, ReadState final // IndividualReadComplete(+FIN) --(user calls ReadAsync() & consumes only partial data)-> None // IndividualReadComplete(+FIN) --(user calls ReadAsync() & consumes full data)-> ReadsCompleted // - // PendingRead --(data arrives in event RECV & completes user's ReadAsync())-> None - // PendingRead --(data arrives in event RECV with FIN flag & completes user's ReadAsync() with only partial data)-> None + // PendingRead --(data arrives in event RECV & completes user's ReadAsync())-> PendingReadFinished + // PendingRead --(data arrives in event RECV with FIN flag & completes user's ReadAsync() with only partial data)-> PendingReadFinished // PendingRead --(data arrives in event RECV with FIN flag & completes user's ReadAsync() with full data)-> ReadsCompleted // + // PendingReadFinished --(reading thread awaits ReceiveResettableCompletionSource)-> None + // // Any non-final state --(event PEER_SEND_SHUTDOWN or SHUTDOWN_COMPLETED with ConnectionClosed=false)-> ReadsCompleted // Any non-final state --(event PEER_SEND_ABORT)-> Aborted // Any non-final state --(user calls AbortRead())-> Aborted From 895978dbcb4bd981ff18ef642773653ac47a61a4 Mon Sep 17 00:00:00 2001 From: Radek Zikmund Date: Tue, 26 Apr 2022 20:22:47 +0200 Subject: [PATCH 09/10] Add missing case --- .../src/System/Net/Quic/Implementations/MsQuic/MsQuicStream.cs | 1 + 1 file changed, 1 insertion(+) 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 fd56cd93bb68f9..f0bcce5d8661cd 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 @@ -557,6 +557,7 @@ internal override async ValueTask ReadAsync(Memory destination, Cance switch (initialReadState) { case ReadState.PendingRead: + case ReadState.PendingReadFinished: ex = new InvalidOperationException(SR.Format(SR.net_io_invalidnestedcall, "read")); break; case ReadState.Aborted: From 9d25082f7fd0f4afc033c6c09cbff95c5d06cc63 Mon Sep 17 00:00:00 2001 From: Radek Zikmund Date: Tue, 26 Apr 2022 22:00:21 +0200 Subject: [PATCH 10/10] fixup! Add missing case --- .../System/Net/Quic/Implementations/MsQuic/MsQuicStream.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 f0bcce5d8661cd..1cdf0265c7e390 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 @@ -451,9 +451,9 @@ internal override async ValueTask ReadAsync(Memory destination, Cance abortError = _state.ReadErrorCode; // Failure scenario: pre-canceled token. Transition: Any non-final -> Aborted - // PendingRead state indicates there is another concurrent read operation in flight + // PendingRead or PendingReadFinished state indicates there is another concurrent read operation in flight // which is forbidden, so it is handled separately - if (initialReadState != ReadState.PendingRead && cancellationToken.IsCancellationRequested) + if (initialReadState != ReadState.PendingRead && initialReadState != ReadState.PendingReadFinished && cancellationToken.IsCancellationRequested) { initialReadState = ReadState.Aborted; CleanupReadStateAndCheckPending(_state, ReadState.Aborted);