Пример #1
0
        public async Task OpenStreamAsync_BlocksUntilAvailable(bool unidirectional)
        {
            ValueTask <QuicStream> OpenStreamAsync(QuicConnection connection) => unidirectional
                ? connection.OpenUnidirectionalStreamAsync()
                : connection.OpenBidirectionalStreamAsync();

            QuicListenerOptions listenerOptions = CreateQuicListenerOptions();

            listenerOptions.MaxUnidirectionalStreams = 1;
            listenerOptions.MaxBidirectionalStreams  = 1;
            (QuicConnection clientConnection, QuicConnection serverConnection) = await CreateConnectedQuicConnection(null, listenerOptions);

            // Open one stream, second call should block
            QuicStream stream = await OpenStreamAsync(clientConnection);

            ValueTask <QuicStream> waitTask = OpenStreamAsync(clientConnection);

            Assert.False(waitTask.IsCompleted);

            // Close the streams, the waitTask should finish as a result.
            stream.Dispose();
            QuicStream newStream = await serverConnection.AcceptStreamAsync();

            newStream.Dispose();

            newStream = await waitTask.AsTask().WaitAsync(TimeSpan.FromSeconds(10));

            newStream.Dispose();

            clientConnection.Dispose();
            serverConnection.Dispose();
        }
Пример #2
0
        public async Task WaitForAvailableBidirectionStreamsAsyncWorks()
        {
            QuicListenerOptions listenerOptions = CreateQuicListenerOptions();

            listenerOptions.MaxBidirectionalStreams = 1;
            (QuicConnection clientConnection, QuicConnection serverConnection) = await CreateConnectedQuicConnection(null, listenerOptions);

            // No stream opened yet, should return immediately.
            Assert.True(clientConnection.WaitForAvailableBidirectionalStreamsAsync().IsCompletedSuccessfully);

            // Open one stream, should wait till it closes.
            QuicStream stream   = clientConnection.OpenBidirectionalStream();
            ValueTask  waitTask = clientConnection.WaitForAvailableBidirectionalStreamsAsync();

            Assert.False(waitTask.IsCompleted);
            Assert.Throws <QuicException>(() => clientConnection.OpenBidirectionalStream());

            // Close the streams, the waitTask should finish as a result.
            stream.Dispose();
            QuicStream newStream = await serverConnection.AcceptStreamAsync();

            newStream.Dispose();
            await waitTask.AsTask().WaitAsync(TimeSpan.FromSeconds(10));

            clientConnection.Dispose();
            serverConnection.Dispose();
        }
Пример #3
0
        public async Task WaitForAvailableBidirectionStreamsAsyncWorks()
        {
            using QuicListener listener           = CreateQuicListener(maxBidirectionalStreams: 1);
            using QuicConnection clientConnection = CreateQuicConnection(listener.ListenEndPoint);

            Task <QuicConnection> serverTask = listener.AcceptConnectionAsync().AsTask();
            await TaskTimeoutExtensions.WhenAllOrAnyFailed(clientConnection.ConnectAsync().AsTask(), serverTask, PassingTestTimeoutMilliseconds);

            using QuicConnection serverConnection = serverTask.Result;

            // No stream opened yet, should return immediately.
            Assert.True(clientConnection.WaitForAvailableBidirectionalStreamsAsync().IsCompletedSuccessfully);

            // Open one stream, should wait till it closes.
            QuicStream stream   = clientConnection.OpenBidirectionalStream();
            ValueTask  waitTask = clientConnection.WaitForAvailableBidirectionalStreamsAsync();

            Assert.False(waitTask.IsCompleted);
            Assert.Throws <QuicException>(() => clientConnection.OpenBidirectionalStream());

            // Close the streams, the waitTask should finish as a result.
            stream.Dispose();
            QuicStream newStream = await serverConnection.AcceptStreamAsync();

            newStream.Dispose();
            await waitTask.AsTask().WaitAsync(TimeSpan.FromSeconds(10));
        }
Пример #4
0
        public async Task WaitForAvailableUnidirectionStreamsAsyncWorks()
        {
            using QuicListener listener           = CreateQuicListener(maxUnidirectionalStreams: 1);
            using QuicConnection clientConnection = CreateQuicConnection(listener.ListenEndPoint);

            ValueTask clientTask = clientConnection.ConnectAsync();

            using QuicConnection serverConnection = await listener.AcceptConnectionAsync();

            await clientTask;

            // No stream opened yet, should return immediately.
            Assert.True(clientConnection.WaitForAvailableUnidirectionalStreamsAsync().IsCompletedSuccessfully);

            // Open one stream, should wait till it closes.
            QuicStream stream   = clientConnection.OpenUnidirectionalStream();
            ValueTask  waitTask = clientConnection.WaitForAvailableUnidirectionalStreamsAsync();

            Assert.False(waitTask.IsCompleted);
            Assert.Throws <QuicException>(() => clientConnection.OpenUnidirectionalStream());

            // Close the stream, the waitTask should finish as a result.
            stream.Dispose();
            await waitTask.AsTask().WaitAsync(TimeSpan.FromSeconds(10));
        }
Пример #5
0
        public override async ValueTask DisposeAsync()
        {
            Transport.Input.Complete();
            Transport.Output.Complete();

            await _processingTask;

            _stream.Dispose();

            _streamClosedTokenSource.Dispose();
        }
Пример #6
0
        /// <summary>
        /// Called when shutting down, this checks for when shutdown is complete (no more active requests) and does actual disposal.
        /// </summary>
        /// <remarks>Requires <see cref="SyncObj"/> to be locked.</remarks>
        private void CheckForShutdown()
        {
            Debug.Assert(Monitor.IsEntered(SyncObj));
            Debug.Assert(ShuttingDown);

            if (_activeRequests.Count != 0)
            {
                return;
            }

            if (_clientControl != null)
            {
                _clientControl.Dispose();
                _clientControl = null;
            }

            if (_connection != null)
            {
                // Close the QuicConnection in the background.

                if (_connectionClosedTask == null)
                {
                    _connectionClosedTask = _connection.CloseAsync((long)Http3ErrorCode.NoError).AsTask();
                }

                QuicConnection connection = _connection;
                _connection = null;

                _ = _connectionClosedTask.ContinueWith(closeTask =>
                {
                    if (closeTask.IsFaulted && NetEventSource.IsEnabled)
                    {
                        Trace($"{nameof(QuicConnection)} failed to close: {closeTask.Exception.InnerException}");
                    }

                    try
                    {
                        connection.Dispose();
                    }
                    catch (Exception ex)
                    {
                        Trace($"{nameof(QuicConnection)} failed to dispose: {ex}");
                    }
                }, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
            }
        }
Пример #7
0
        private Task <HttpResponseMessage> SendWithoutWaitingAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            QuicStream         quicStream = null;
            Http3RequestStream stream;

            try
            {
                // TODO: do less work in this lock, try to get rid of goto.
                lock (SyncObj)
                {
                    if (_connection == null)
                    {
                        goto retryRequest;
                    }

                    quicStream = _connection.OpenBidirectionalStream();
                    if (_lastProcessedStreamId != -1 && quicStream.StreamId > _lastProcessedStreamId)
                    {
                        goto retryRequest;
                    }

                    stream = new Http3RequestStream(request, this, quicStream);
                    _activeRequests.Add(quicStream.StreamId, stream);
                }

                return(stream.SendAsync(cancellationToken));
            }
            catch (QuicConnectionAbortedException ex)
            {
                // This will happen if we aborted _connection somewhere.
                Abort(ex);
            }

retryRequest:
            // We lost a race between GOAWAY/abort and our pool sending the request to this connection.
            quicStream?.Dispose();
            return(Task.FromException <HttpResponseMessage>(new HttpRequestException(SR.net_http_request_aborted, null, RequestRetryType.RetryOnSameOrNextProxy)));
        }
Пример #8
0
        /// <summary>
        /// Called when shutting down, this checks for when shutdown is complete (no more active requests) and does actual disposal.
        /// </summary>
        /// <remarks>Requires <see cref="SyncObj"/> to be locked.</remarks>
        private void CheckForShutdown()
        {
            Debug.Assert(Monitor.IsEntered(SyncObj));
            Debug.Assert(ShuttingDown);

            if (_activeRequests.Count != 0)
            {
                return;
            }

            if (_clientControl != null)
            {
                _clientControl.Dispose();
                _clientControl = null;
            }

            if (_connection != null)
            {
                _connection.CloseAsync((long)Http3ErrorCode.NoError).GetAwaiter().GetResult(); // TODO: async...
                _connection.Dispose();
                _connection = null;
            }
        }
Пример #9
0
 public void Dispose()
 {
     _stream.Dispose();
 }
Пример #10
0
        /// <summary>
        /// Routes a stream to an appropriate stream-type-specific processor
        /// </summary>
        private async Task ProcessServerStreamAsync(QuicStream stream)
        {
            ArrayBuffer buffer = default;

            try
            {
                await using (stream.ConfigureAwait(false))
                {
                    if (stream.CanWrite)
                    {
                        // Server initiated bidirectional streams are either push streams or extensions, and we support neither.
                        throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.StreamCreationError);
                    }

                    buffer = new ArrayBuffer(initialSize: 32, usePool: true);

                    int bytesRead;

                    try
                    {
                        bytesRead = await stream.ReadAsync(buffer.AvailableMemory, CancellationToken.None).ConfigureAwait(false);
                    }
                    catch (QuicException ex) when(ex.QuicError == QuicError.StreamAborted)
                    {
                        // Treat identical to receiving 0. See below comment.
                        bytesRead = 0;
                    }

                    if (bytesRead == 0)
                    {
                        // https://quicwg.org/base-drafts/draft-ietf-quic-http.html#name-unidirectional-streams
                        // A sender can close or reset a unidirectional stream unless otherwise specified. A receiver MUST
                        // tolerate unidirectional streams being closed or reset prior to the reception of the unidirectional
                        // stream header.
                        return;
                    }

                    buffer.Commit(bytesRead);

                    // Stream type is a variable-length integer, but we only check the first byte. There is no known type requiring more than 1 byte.
                    switch (buffer.ActiveSpan[0])
                    {
                    case (byte)Http3StreamType.Control:
                        if (Interlocked.Exchange(ref _haveServerControlStream, 1) != 0)
                        {
                            // A second control stream has been received.
                            throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.StreamCreationError);
                        }

                        // Discard the stream type header.
                        buffer.Discard(1);

                        // Ownership of buffer is transferred to ProcessServerControlStreamAsync.
                        ArrayBuffer bufferCopy = buffer;
                        buffer = default;

                        await ProcessServerControlStreamAsync(stream, bufferCopy).ConfigureAwait(false);

                        return;

                    case (byte)Http3StreamType.QPackDecoder:
                        if (Interlocked.Exchange(ref _haveServerQpackDecodeStream, 1) != 0)
                        {
                            // A second QPack decode stream has been received.
                            throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.StreamCreationError);
                        }

                        // The stream must not be closed, but we aren't using QPACK right now -- ignore.
                        buffer.Dispose();
                        await stream.CopyToAsync(Stream.Null).ConfigureAwait(false);

                        return;

                    case (byte)Http3StreamType.QPackEncoder:
                        if (Interlocked.Exchange(ref _haveServerQpackEncodeStream, 1) != 0)
                        {
                            // A second QPack encode stream has been received.
                            throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.StreamCreationError);
                        }

                        // We haven't enabled QPack in our SETTINGS frame, so we shouldn't receive any meaningful data here.
                        // However, the standard says the stream must not be closed for the lifetime of the connection. Just ignore any data.
                        buffer.Dispose();
                        await stream.CopyToAsync(Stream.Null).ConfigureAwait(false);

                        return;

                    case (byte)Http3StreamType.Push:
                        // We don't support push streams.
                        // Because no maximum push stream ID was negotiated via a MAX_PUSH_ID frame, server should not have sent this. Abort the connection with H3_ID_ERROR.
                        throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.IdError);

                    default:
                        // Unknown stream type. Per spec, these must be ignored and aborted but not be considered a connection-level error.

                        if (NetEventSource.Log.IsEnabled())
                        {
                            // Read the rest of the integer, which might be more than 1 byte, so we can log it.

                            long unknownStreamType;
                            while (!VariableLengthIntegerHelper.TryRead(buffer.ActiveSpan, out unknownStreamType, out _))
                            {
                                buffer.EnsureAvailableSpace(VariableLengthIntegerHelper.MaximumEncodedLength);
                                bytesRead = await stream.ReadAsync(buffer.AvailableMemory, CancellationToken.None).ConfigureAwait(false);

                                if (bytesRead == 0)
                                {
                                    unknownStreamType = -1;
                                    break;
                                }

                                buffer.Commit(bytesRead);
                            }

                            NetEventSource.Info(this, $"Ignoring server-initiated stream of unknown type {unknownStreamType}.");
                        }

                        stream.Abort(QuicAbortDirection.Read, (long)Http3ErrorCode.StreamCreationError);
                        stream.Dispose();
                        return;
                    }
                }
            }
            catch (QuicException ex) when(ex.QuicError == QuicError.OperationAborted)
            {
                // ignore the exception, we have already closed the connection
            }
            catch (Exception ex)
            {
                Abort(ex);
            }
            finally
            {
                buffer.Dispose();
            }
        }