예제 #1
0
        /// <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);
        }
예제 #2
0
        /// <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());
        }
예제 #3
0
        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));
            }
        }
예제 #4
0
        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();
            }
        }