diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3RequestStream.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3RequestStream.cs index 11c74bd1571..9382047b6fb 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3RequestStream.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3RequestStream.cs @@ -172,15 +172,9 @@ namespace System.Net.Http await FlushSendBufferAsync(endStream: _request.Content == null, _requestBodyCancellationSource.Token).ConfigureAwait(false); } - Task sendRequestTask; - if (_request.Content != null) - { - sendRequestTask = SendContentAsync(_request.Content!, _requestBodyCancellationSource.Token); - } - else - { - sendRequestTask = Task.CompletedTask; - } + Task sendRequestTask = _request.Content != null + ? SendContentAsync(_request.Content, _requestBodyCancellationSource.Token) + : Task.CompletedTask; // In parallel, send content and read response. // Depending on Expect 100 Continue usage, one will depend on the other making progress. @@ -219,6 +213,23 @@ namespace System.Net.Http // Wait for the response headers to be read. await readResponseTask.ConfigureAwait(false); + // If we've sent a body, wait for the writes to be closed (most likely already done). + // If sendRequestTask hasn't completed yet, we're doing duplex content transfers and can't wait for writes to be closed yet. + if (sendRequestTask.IsCompletedSuccessfully && + _stream.WritesClosed is { IsCompletedSuccessfully: false } writesClosed) + { + try + { + await writesClosed.WaitAsync(_requestBodyCancellationSource.Token).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + // If the request got cancelled before WritesClosed completed, avoid leaking an unobserved task exception. + _connection.LogExceptions(writesClosed); + throw; + } + } + Debug.Assert(_response != null && _response.Content != null); // Set our content stream. var responseContent = (HttpConnectionResponseContent)_response.Content; @@ -460,7 +471,6 @@ namespace System.Net.Http else { _stream.CompleteWrites(); - await _stream.WritesClosed.ConfigureAwait(false); } if (HttpTelemetry.Log.IsEnabled()) HttpTelemetry.Log.RequestContentStop(bytesWritten); @@ -523,17 +533,11 @@ namespace System.Net.Http } } - private async ValueTask FlushSendBufferAsync(bool endStream, CancellationToken cancellationToken) + private ValueTask FlushSendBufferAsync(bool endStream, CancellationToken cancellationToken) { - await _stream.WriteAsync(_sendBuffer.ActiveMemory, endStream, cancellationToken).ConfigureAwait(false); - _sendBuffer.Discard(_sendBuffer.ActiveLength); - - await _stream.FlushAsync(cancellationToken).ConfigureAwait(false); - - if (endStream) - { - await _stream.WritesClosed.ConfigureAwait(false); - } + ReadOnlyMemory toSend = _sendBuffer.ActiveMemory; + _sendBuffer.Discard(toSend.Length); + return _stream.WriteAsync(toSend, endStream, cancellationToken); } private async ValueTask DrainContentLength0Frames(CancellationToken cancellationToken) 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 8fb8d90a7b3..c53fb1b0741 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 @@ -310,6 +310,13 @@ internal sealed class ResettableValueTaskSource : IValueTaskSource { if (_finalTaskSource is null) { + if (_isSignaled) + { + return _exception is null + ? Task.CompletedTask + : Task.FromException(_exception); + } + _finalTaskSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); if (!_isCompleted) { @@ -319,10 +326,6 @@ internal sealed class ResettableValueTaskSource : IValueTaskSource ((GCHandle)state!).Free(); }, handle, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); } - if (_isSignaled) - { - TrySignal(out _); - } } return _finalTaskSource.Task; }