/// <summary>Enqueues a waiter to the waiters list.</summary> private TaskCompletionSourceWithCancellation <HttpConnection> EnqueueWaiter() { Debug.Assert(Monitor.IsEntered(SyncObj)); Debug.Assert(Settings._maxConnectionsPerServer != int.MaxValue); Debug.Assert(_idleConnections.Count == 0, $"With {_idleConnections.Count} idle connections, we shouldn't have a waiter."); if (_waiters == null) { _waiters = new Queue <TaskCompletionSourceWithCancellation <HttpConnection> >(); } var waiter = new TaskCompletionSourceWithCancellation <HttpConnection>(); _waiters.Enqueue(waiter); return(waiter); }
/// <summary> /// Waits for MAX_STREAMS to be raised by the server. /// </summary> private Task WaitForAvailableRequestStreamAsync(CancellationToken cancellationToken) { TaskCompletionSourceWithCancellation <bool> tcs; lock (SyncObj) { long remaining = _requestStreamsRemaining; if (remaining > 0) { _requestStreamsRemaining = remaining - 1; return(Task.CompletedTask); } tcs = new TaskCompletionSourceWithCancellation <bool>(); _waitingRequests.Enqueue(tcs); } // Note: cancellation on connection shutdown is handled in CancelWaiters. return(tcs.WaitWithCancellationAsync(cancellationToken).AsTask()); }
public ValueTask <int> RequestCreditAsync(int amount, CancellationToken cancellationToken) { lock (SyncObject) { if (_disposed) { throw new ObjectDisposedException(nameof(CreditManager)); } if (_current > 0) { Debug.Assert(_waiters == null || _waiters.Count == 0, "Shouldn't have waiters when credit is available"); int granted = Math.Min(amount, _current); _current -= granted; return(new ValueTask <int>(granted)); } // Uses RunContinuationsAsynchronously internally. var tcs = new TaskCompletionSourceWithCancellation <int>(); if (_waiters == null) { _waiters = new Queue <Waiter>(); } Waiter waiter = new Waiter { Amount = amount, TaskCompletionSource = tcs }; _waiters.Enqueue(waiter); return(new ValueTask <int>(cancellationToken.CanBeCanceled ? tcs.WaitWithCancellationAsync(cancellationToken) : tcs.Task)); } }
public override async Task <HttpResponseMessage> SendAsync(HttpRequestMessage request, bool async, CancellationToken cancellationToken) { // Wait for an available stream (based on QUIC MAX_STREAMS) if there isn't one available yet. TaskCompletionSourceWithCancellation <bool>?waitForAvailableStreamTcs = null; lock (SyncObj) { long remaining = _requestStreamsRemaining; if (remaining > 0) { _requestStreamsRemaining = remaining - 1; } else { waitForAvailableStreamTcs = new TaskCompletionSourceWithCancellation <bool>(); _waitingRequests.Enqueue(waitForAvailableStreamTcs); } } if (waitForAvailableStreamTcs != null) { await waitForAvailableStreamTcs.WaitWithCancellationAsync(cancellationToken).ConfigureAwait(false); } // Allocate an active request QuicStream? quicStream = null; Http3RequestStream?requestStream = null; try { lock (SyncObj) { if (_connection != null) { quicStream = _connection.OpenBidirectionalStream(); requestStream = new Http3RequestStream(request, this, quicStream); _activeRequests.Add(quicStream, requestStream); } } if (quicStream == null) { throw new HttpRequestException(SR.net_http_request_aborted, null, RequestRetryType.RetryOnSameOrNextProxy); } // 0-byte write to force QUIC to allocate a stream ID. await quicStream.WriteAsync(Array.Empty <byte>(), cancellationToken).ConfigureAwait(false); requestStream !.StreamId = quicStream.StreamId; bool goAway; lock (SyncObj) { goAway = _lastProcessedStreamId != -1 && requestStream.StreamId > _lastProcessedStreamId; } if (goAway) { throw new HttpRequestException(SR.net_http_request_aborted, null, RequestRetryType.RetryOnSameOrNextProxy); } Task <HttpResponseMessage> responseTask = requestStream.SendAsync(cancellationToken); // null out requestStream to avoid disposing in finally block. It is now in charge of disposing itself. requestStream = null; return(await responseTask.ConfigureAwait(false)); } catch (QuicConnectionAbortedException ex) { // This will happen if we aborted _connection somewhere. Abort(ex); throw new HttpRequestException(SR.Format(SR.net_http_http3_connection_error, ex.ErrorCode), ex, RequestRetryType.RetryOnSameOrNextProxy); } finally { requestStream?.Dispose(); } }