Skip to content

Commit

Permalink
Attempt to fix still running write while disposing the stream in case…
Browse files Browse the repository at this point in the history
… of a cancellation
  • Loading branch information
ManickaP committed Sep 5, 2023
1 parent e4c2f17 commit d6f5061
Showing 1 changed file with 30 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ internal sealed class Http3RequestStream : IHttpStreamHeadersHandler, IAsyncDisp
private TaskCompletionSource<bool>? _expect100ContinueCompletionSource; // True indicates we should send content (e.g. received 100 Continue).
private bool _disposed;
private readonly CancellationTokenSource _requestBodyCancellationSource;
private Task? _sendRequestTask; // Set with SendContentAsync, must be awaited before QuicStream.DisposeAsync();
private Task? _readResponseTask; // Set with ReadResponseAsync, must be awaited before QuicStream.DisposeAsync();

// Allocated when we receive a :status header.
private HttpResponseMessage? _response;
Expand Down Expand Up @@ -107,9 +109,25 @@ public async ValueTask DisposeAsync()
{
_disposed = true;
AbortStream();
// We aborted both sides, thus both task should unblock and should be finished before disposing the QuicStream.
await AwaitUnfinished(_sendRequestTask).ConfigureAwait(false);
await AwaitUnfinished(_readResponseTask).ConfigureAwait(false);
await _stream.DisposeAsync().ConfigureAwait(false);
DisposeSyncHelper();
}

static async ValueTask AwaitUnfinished(Task? task)
{
if (task is not null && !task.IsCompleted)
{
try
{
await task.ConfigureAwait(false);
}
catch // Exceptions from both tasks are logged via _connection.LogException() in case they're not awaited in SendAsync, so the exception can be ignored here.
{ }
}
}
}

private void DisposeSyncHelper()
Expand Down Expand Up @@ -158,52 +176,51 @@ public async Task<HttpResponseMessage> SendAsync(CancellationToken cancellationT
await FlushSendBufferAsync(endStream: _request.Content == null, _requestBodyCancellationSource.Token).ConfigureAwait(false);
}

Task sendContentTask;
if (_request.Content != null)
{
sendContentTask = SendContentAsync(_request.Content!, _requestBodyCancellationSource.Token);
_sendRequestTask = SendContentAsync(_request.Content!, _requestBodyCancellationSource.Token);
}
else
{
sendContentTask = Task.CompletedTask;
_sendRequestTask = Task.CompletedTask;
}

// In parallel, send content and read response.
// Depending on Expect 100 Continue usage, one will depend on the other making progress.
Task readResponseTask = ReadResponseAsync(_requestBodyCancellationSource.Token);
_readResponseTask = ReadResponseAsync(_requestBodyCancellationSource.Token);
bool sendContentObserved = false;

// If we're not doing duplex, wait for content to finish sending here.
// If we are doing duplex and have the unlikely event that it completes here, observe the result.
// See Http2Connection.SendAsync for a full comment on this logic -- it is identical behavior.
if (sendContentTask.IsCompleted ||
if (_sendRequestTask.IsCompleted ||
_request.Content?.AllowDuplex != true ||
await Task.WhenAny(sendContentTask, readResponseTask).ConfigureAwait(false) == sendContentTask ||
sendContentTask.IsCompleted)
await Task.WhenAny(_sendRequestTask, _readResponseTask).ConfigureAwait(false) == _sendRequestTask ||
_sendRequestTask.IsCompleted)
{
try
{
await sendContentTask.ConfigureAwait(false);
await _sendRequestTask.ConfigureAwait(false);
sendContentObserved = true;
}
catch
{
// Exceptions will be bubbled up from sendContentTask here,
// which means the result of readResponseTask won't be observed directly:
// Exceptions will be bubbled up from _sendRequestTask here,
// which means the result of _readResponseTask won't be observed directly:
// Do a background await to log any exceptions.
_connection.LogExceptions(readResponseTask);
_connection.LogExceptions(_readResponseTask);
throw;
}
}
else
{
// Duplex is being used, so we can't wait for content to finish sending.
// Do a background await to log any exceptions.
_connection.LogExceptions(sendContentTask);
_connection.LogExceptions(_sendRequestTask);
}

// Wait for the response headers to be read.
await readResponseTask.ConfigureAwait(false);
await _readResponseTask.ConfigureAwait(false);

Debug.Assert(_response != null && _response.Content != null);
// Set our content stream.
Expand Down

0 comments on commit d6f5061

Please sign in to comment.