diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/ResettableValueTaskSource.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/ResettableValueTaskSource.cs index c53fb1b0741425..f21521eabe5ccd 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/ResettableValueTaskSource.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/ResettableValueTaskSource.cs @@ -52,7 +52,7 @@ public ResettableValueTaskSource() public Action CancellationAction { init { _cancellationAction = value; } } /// - /// Returns true is this task source has entered its final state, i.e. or + /// Returns true is this task source has entered its final state, i.e. or /// was called with final set to true and the result was propagated. /// public bool IsCompleted => (State)Volatile.Read(ref Unsafe.As(ref _state)) == State.Completed; @@ -173,6 +173,7 @@ private bool TryComplete(Exception? exception, bool final) // Unblock the current task source and in case of a final also the final task source. if (exception is not null) { + Debug.Assert(final); // Set up the exception stack trace for the caller. exception = exception.StackTrace is null ? ExceptionDispatchInfo.SetCurrentStackTrace(exception) : exception; if (state is State.None or State.Awaiting) @@ -192,7 +193,7 @@ private bool TryComplete(Exception? exception, bool final) if (_finalTaskSource.TryComplete(exception)) { // Signal the final task only if we don't have another result in the value task source. - // In that case, the final task will be signalled after the value task result is retrieved. + // In that case, the final task will be signaled after the value task result is retrieved. if (state != State.Ready) { _finalTaskSource.TrySignal(out _); @@ -226,15 +227,14 @@ public bool TrySetResult(bool final = false) } /// - /// Tries to transition from to either or , depending on the value of . - /// Only the first call is able to do that with the exception of TrySetResult() followed by TrySetResult(true), which will both return true. + /// Tries to transition from to , setting an exception. + /// Only the first call is able to do that. /// - /// Whether this is the final transition to or just a transition into from which the task source can be reset back to . /// The exception to set as a result of the value task. /// true if this is the first call that set the result; otherwise, false. - public bool TrySetException(Exception exception, bool final = false) + public bool TrySetException(Exception exception) { - return TryComplete(exception, final); + return TryComplete(exception, final: true); } ValueTaskSourceStatus IValueTaskSource.GetStatus(short token) @@ -297,6 +297,7 @@ private struct FinalTaskSource private bool _isCompleted; private bool _isSignaled; private Exception? _exception; + private Task? _signaledTask; public FinalTaskSource() { @@ -312,9 +313,9 @@ public Task GetTask(object? keepAlive) { if (_isSignaled) { - return _exception is null - ? Task.CompletedTask - : Task.FromException(_exception); + _signaledTask ??= _exception is null ? Task.CompletedTask : Task.FromException(_exception); + _ = _signaledTask.Exception; // Observe the exception. + return _signaledTask; } _finalTaskSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -350,13 +351,17 @@ public bool TrySignal(out Exception? exception) return false; } - if (_exception is not null) + if (_finalTaskSource is not null) { - _finalTaskSource?.SetException(_exception); - } - else - { - _finalTaskSource?.SetResult(); + if (_exception is not null) + { + _finalTaskSource.SetException(_exception); + _ = _finalTaskSource.Task.Exception; // Observe the exception. + } + else + { + _finalTaskSource.SetResult(); + } } exception = _exception; 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 ce0128e779e9ac..04e4bb94f30bf6 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 @@ -430,7 +430,7 @@ public ValueTask WriteAsync(ReadOnlyMemory buffer, bool completeWrites, Ca exception = Volatile.Read(ref _sendException); if (exception is not null) { - _sendTcs.TrySetException(exception, final: true); + _sendTcs.TrySetException(exception); } } // SEND_COMPLETE expected, buffer and lock will be released then. @@ -487,7 +487,7 @@ public void Abort(QuicAbortDirection abortDirection, long errorCode) if (abortDirection.HasFlag(QuicAbortDirection.Read)) { - _receiveTcs.TrySetException(ThrowHelper.GetOperationAbortedException(SR.net_quic_reading_aborted), final: true); + _receiveTcs.TrySetException(ThrowHelper.GetOperationAbortedException(SR.net_quic_reading_aborted)); } if (abortDirection.HasFlag(QuicAbortDirection.Write)) { @@ -495,7 +495,7 @@ public void Abort(QuicAbortDirection abortDirection, long errorCode) Interlocked.CompareExchange(ref _sendException, exception, null); if (Interlocked.CompareExchange(ref _sendLocked, 1, 0) == 0) { - _sendTcs.TrySetException(_sendException, final: true); + _sendTcs.TrySetException(_sendException); Volatile.Write(ref _sendLocked, 0); } } @@ -585,7 +585,7 @@ private unsafe int HandleEventSendComplete(ref SEND_COMPLETE_DATA data) Exception? exception = Volatile.Read(ref _sendException); if (exception is not null) { - _sendTcs.TrySetException(exception, final: true); + _sendTcs.TrySetException(exception); } if (data.Canceled == 0) { @@ -604,12 +604,12 @@ private unsafe int HandleEventPeerSendShutdown() } private unsafe int HandleEventPeerSendAborted(ref PEER_SEND_ABORTED_DATA data) { - _receiveTcs.TrySetException(ThrowHelper.GetStreamAbortedException((long)data.ErrorCode), final: true); + _receiveTcs.TrySetException(ThrowHelper.GetStreamAbortedException((long)data.ErrorCode)); return QUIC_STATUS_SUCCESS; } private unsafe int HandleEventPeerReceiveAborted(ref PEER_RECEIVE_ABORTED_DATA data) { - _sendTcs.TrySetException(ThrowHelper.GetStreamAbortedException((long)data.ErrorCode), final: true); + _sendTcs.TrySetException(ThrowHelper.GetStreamAbortedException((long)data.ErrorCode)); return QUIC_STATUS_SUCCESS; } private unsafe int HandleEventSendShutdownComplete(ref SEND_SHUTDOWN_COMPLETE_DATA data) @@ -639,8 +639,8 @@ private unsafe int HandleEventShutdownComplete(ref SHUTDOWN_COMPLETE_DATA data) (shutdownByApp: false, closedRemotely: false) => ThrowHelper.GetExceptionForMsQuicStatus(data.ConnectionCloseStatus, (long)data.ConnectionErrorCode), }; _startedTcs.TrySetException(exception); - _receiveTcs.TrySetException(exception, final: true); - _sendTcs.TrySetException(exception, final: true); + _receiveTcs.TrySetException(exception); + _sendTcs.TrySetException(exception); } _startedTcs.TrySetException(ThrowHelper.GetOperationAbortedException()); _shutdownTcs.TrySetResult(); @@ -766,11 +766,11 @@ unsafe void StreamShutdown(QUIC_STREAM_SHUTDOWN_FLAGS flags, long errorCode) { if (flags.HasFlag(QUIC_STREAM_SHUTDOWN_FLAGS.ABORT_RECEIVE) && !_receiveTcs.IsCompleted) { - _receiveTcs.TrySetException(ThrowHelper.GetOperationAbortedException(SR.net_quic_reading_aborted), final: true); + _receiveTcs.TrySetException(ThrowHelper.GetOperationAbortedException(SR.net_quic_reading_aborted)); } if (flags.HasFlag(QUIC_STREAM_SHUTDOWN_FLAGS.ABORT_SEND) && !_sendTcs.IsCompleted) { - _sendTcs.TrySetException(ThrowHelper.GetOperationAbortedException(SR.net_quic_writing_aborted), final: true); + _sendTcs.TrySetException(ThrowHelper.GetOperationAbortedException(SR.net_quic_writing_aborted)); } } }