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(); }
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(); }
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)); }
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)); }
public override async ValueTask DisposeAsync() { Transport.Input.Complete(); Transport.Output.Complete(); await _processingTask; _stream.Dispose(); _streamClosedTokenSource.Dispose(); }
/// <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); } }
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))); }
/// <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; } }
public void Dispose() { _stream.Dispose(); }
/// <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(); } }