internal async Task InnerProcessRequestsAsync <TContext>(IHttpApplication <TContext> application) where TContext : notnull { // Start other three unidirectional streams here. var controlTask = CreateControlStream(application); var encoderTask = CreateEncoderStream(application); var decoderTask = CreateDecoderStream(application); try { while (true) { var streamContext = await _multiplexedContext.AcceptAsync(); if (streamContext == null || _haveSentGoAway) { break; } var quicStreamFeature = streamContext.Features.Get <IStreamDirectionFeature>(); var streamIdFeature = streamContext.Features.Get <IStreamIdFeature>(); Debug.Assert(quicStreamFeature != null); var httpConnectionContext = new Http3StreamContext( streamContext.ConnectionId, protocols: default,
public static async Task <QuicStreamContext> CreateAndCompleteBidirectionalStreamGracefully(QuicConnection clientConnection, MultiplexedConnectionContext serverConnection) { var clientStream = clientConnection.OpenBidirectionalStream(); await clientStream.WriteAsync(TestData, endStream : true).DefaultTimeout(); var serverStream = await serverConnection.AcceptAsync().DefaultTimeout(); var readResult = await serverStream.Transport.Input.ReadAtLeastAsync(TestData.Length).DefaultTimeout(); serverStream.Transport.Input.AdvanceTo(readResult.Buffer.End); // Input should be completed. readResult = await serverStream.Transport.Input.ReadAsync(); Assert.True(readResult.IsCompleted); // Complete reading and writing. await serverStream.Transport.Input.CompleteAsync(); await serverStream.Transport.Output.CompleteAsync(); var quicStreamContext = Assert.IsType <QuicStreamContext>(serverStream); // Both send and receive loops have exited. await quicStreamContext._processingTask.DefaultTimeout(); Assert.True(quicStreamContext.CanWrite); Assert.True(quicStreamContext.CanRead); await quicStreamContext.DisposeAsync(); quicStreamContext.Dispose(); return(quicStreamContext); }
public async Task GracefulServerShutdownSendsGoawayClosesConnection() { await InitializeConnectionAsync(_echoApplication); // Trigger server shutdown. MultiplexedConnectionContext.ConnectionClosingCts.Cancel(); Assert.Null(await MultiplexedConnectionContext.AcceptAsync().DefaultTimeout()); }
public async Task GracefulServerShutdownClosesConnection() { await InitializeConnectionAsync(_echoApplication); var inboundControlStream = await GetInboundControlStream(); await inboundControlStream.ExpectSettingsAsync(); // Trigger server shutdown. CloseConnectionGracefully(); Assert.Null(await MultiplexedConnectionContext.AcceptAsync().DefaultTimeout()); await WaitForConnectionStopAsync(0, false, expectedErrorCode : Http3ErrorCode.NoError); }
public static async Task <QuicStreamContext> CreateAndCompleteBidirectionalStreamGracefully(QuicConnection clientConnection, MultiplexedConnectionContext serverConnection, ILogger logger) { logger.LogInformation("Client starting stream."); var clientStream = await clientConnection.OpenOutboundStreamAsync(QuicStreamType.Bidirectional); logger.LogInformation("Client sending data."); await clientStream.WriteAsync(TestData, completeWrites : true).DefaultTimeout(); logger.LogInformation("Server accepting stream."); var serverStream = await serverConnection.AcceptAsync().DefaultTimeout(); logger.LogInformation("Server reading data."); var readResult = await serverStream.Transport.Input.ReadAtLeastAsync(TestData.Length).DefaultTimeout(); serverStream.Transport.Input.AdvanceTo(readResult.Buffer.End); // Input should be completed. readResult = await serverStream.Transport.Input.ReadAsync(); Assert.True(readResult.IsCompleted); // Complete reading and writing. logger.LogInformation("Server completing input and output."); await serverStream.Transport.Input.CompleteAsync(); await serverStream.Transport.Output.CompleteAsync(); var quicStreamContext = Assert.IsType <QuicStreamContext>(serverStream); // Both send and receive loops have exited. logger.LogInformation("Server verifying stream is finished."); await quicStreamContext._processingTask.DefaultTimeout(); Assert.True(quicStreamContext.CanWrite); Assert.True(quicStreamContext.CanRead); logger.LogInformation("Server disposing stream."); await quicStreamContext.DisposeAsync(); quicStreamContext.Dispose(); return(quicStreamContext); }
public async Task ProcessRequestsAsync <TContext>(IHttpApplication <TContext> application) where TContext : notnull { // An endpoint MAY avoid creating an encoder stream if it's not going to // be used(for example if its encoder doesn't wish to use the dynamic // table, or if the maximum size of the dynamic table permitted by the // peer is zero). // An endpoint MAY avoid creating a decoder stream if its decoder sets // the maximum capacity of the dynamic table to zero. // Don't create Encoder and Decoder as they aren't used now. Exception? error = null; Http3ControlStream?outboundControlStream = null; ValueTask outboundControlStreamTask = default; bool clientAbort = false; try { outboundControlStream = await CreateNewUnidirectionalStreamAsync(application); lock (_sync) { OutboundControlStream = outboundControlStream; } // Don't delay on waiting to send outbound control stream settings. outboundControlStreamTask = ProcessOutboundControlStreamAsync(outboundControlStream); while (_stoppedAcceptingStreams == 0) { var streamContext = await _multiplexedContext.AcceptAsync(_acceptStreamsCts.Token); try { // Return null on server close or cancellation. if (streamContext == null) { if (_acceptStreamsCts.Token.IsCancellationRequested) { _acceptStreamsCts = new CancellationTokenSource(); } // There is no stream so continue to skip to UpdateConnectionState in finally. // UpdateConnectionState is responsible for updating connection to // stop accepting streams and break out of accept loop. continue; } var streamDirectionFeature = streamContext.Features.Get <IStreamDirectionFeature>(); var streamIdFeature = streamContext.Features.Get <IStreamIdFeature>(); Debug.Assert(streamDirectionFeature != null); Debug.Assert(streamIdFeature != null); if (!streamDirectionFeature.CanWrite) { // Unidirectional stream var stream = new Http3ControlStream <TContext>(application, CreateHttpStreamContext(streamContext)); _streamLifetimeHandler.OnStreamCreated(stream); ThreadPool.UnsafeQueueUserWorkItem(stream, preferLocal: false); } else { // Request stream // https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-5.2-2 if (_gracefulCloseStarted) { // https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-4.1.2-3 streamContext.Features.Get <IProtocolErrorCodeFeature>() !.Error = (long)Http3ErrorCode.RequestRejected; streamContext.Abort(new ConnectionAbortedException("HTTP/3 connection is closing and no longer accepts new requests.")); await streamContext.DisposeAsync(); continue; } // Request stream IDs are tracked. UpdateHighestOpenedRequestStreamId(streamIdFeature.StreamId); var persistentStateFeature = streamContext.Features.Get <IPersistentStateFeature>(); Debug.Assert(persistentStateFeature != null, $"Required {nameof(IPersistentStateFeature)} not on stream context."); Http3Stream <TContext> stream; // Check whether there is an existing HTTP/3 stream on the transport stream. // A stream will only be cached if the transport stream itself is reused. if (!persistentStateFeature.State.TryGetValue(StreamPersistentStateKey, out var s)) { stream = new Http3Stream <TContext>(application, CreateHttpStreamContext(streamContext)); persistentStateFeature.State.Add(StreamPersistentStateKey, stream); } else { stream = (Http3Stream <TContext>)s !; stream.InitializeWithExistingContext(streamContext.Transport); } _streamLifetimeHandler.OnStreamCreated(stream); KestrelEventSource.Log.RequestQueuedStart(stream, AspNetCore.Http.HttpProtocol.Http3); ThreadPool.UnsafeQueueUserWorkItem(stream, preferLocal: false); } } finally { UpdateConnectionState(); } } } catch (ConnectionResetException ex) { lock (_streams) { if (_activeRequestCount > 0) { Log.RequestProcessingError(_context.ConnectionId, ex); } } error = ex; clientAbort = true; } catch (IOException ex) { Log.RequestProcessingError(_context.ConnectionId, ex); error = ex; } catch (ConnectionAbortedException ex) { Log.RequestProcessingError(_context.ConnectionId, ex); error = ex; } catch (Http3ConnectionErrorException ex) { Log.Http3ConnectionError(_context.ConnectionId, ex); error = ex; } catch (Exception ex) { error = ex; } finally { try { // Don't try to send GOAWAY if the client has already closed the connection. if (!clientAbort) { if (TryStopAcceptingStreams() || _gracefulCloseStarted) { await SendGoAwayAsync(GetCurrentGoAwayStreamId()); } } // Abort active request streams. lock (_streams) { foreach (var stream in _streams.Values) { stream.Abort(CreateConnectionAbortError(error, clientAbort), (Http3ErrorCode)_errorCodeFeature.Error); } } if (outboundControlStream != null) { // Don't gracefully close the outbound control stream. If the peer detects // the control stream closes it will close with a procotol error. // Instead, allow control stream to be automatically aborted when the // connection is aborted. await outboundControlStreamTask; } // Complete Abort(CreateConnectionAbortError(error, clientAbort), (Http3ErrorCode)_errorCodeFeature.Error); // Wait for active requests to complete. while (_activeRequestCount > 0) { await _streamCompletionAwaitable; } _context.TimeoutControl.CancelTimeout(); } catch { Abort(CreateConnectionAbortError(error, clientAbort), Http3ErrorCode.InternalError); throw; } finally { // Connection can close without processing any request streams. var streamId = _highestOpenedRequestStreamId != DefaultHighestOpenedRequestStreamId ? _highestOpenedRequestStreamId : (long?)null; Log.Http3ConnectionClosed(_context.ConnectionId, streamId); } } }
internal async Task InnerProcessRequestsAsync <TContext>(IHttpApplication <TContext> application) { // Start other three unidirectional streams here. var controlTask = CreateControlStream(application); var encoderTask = CreateEncoderStream(application); var decoderTask = CreateDecoderStream(application); try { while (true) { var streamContext = await _multiplexedContext.AcceptAsync(); if (streamContext == null || _haveSentGoAway) { break; } var quicStreamFeature = streamContext.Features.Get <IStreamDirectionFeature>(); var streamIdFeature = streamContext.Features.Get <IStreamIdFeature>(); Debug.Assert(quicStreamFeature != null); var httpConnectionContext = new Http3StreamContext { ConnectionId = streamContext.ConnectionId, StreamContext = streamContext, // TODO connection context is null here. Should we set it to anything? ServiceContext = _context.ServiceContext, ConnectionFeatures = streamContext.Features, MemoryPool = _context.MemoryPool, Transport = streamContext.Transport, TimeoutControl = _context.TimeoutControl, LocalEndPoint = streamContext.LocalEndPoint as IPEndPoint, RemoteEndPoint = streamContext.RemoteEndPoint as IPEndPoint }; if (!quicStreamFeature.CanWrite) { // Unidirectional stream var stream = new Http3ControlStream <TContext>(application, this, httpConnectionContext); ThreadPool.UnsafeQueueUserWorkItem(stream, preferLocal: false); } else { // Keep track of highest stream id seen for GOAWAY var streamId = streamIdFeature.StreamId; HighestStreamId = streamId; var http3Stream = new Http3Stream <TContext>(application, this, httpConnectionContext); var stream = http3Stream; lock (_streams) { _streams[streamId] = http3Stream; } KestrelEventSource.Log.RequestQueuedStart(stream, AspNetCore.Http.HttpProtocol.Http3); ThreadPool.UnsafeQueueUserWorkItem(stream, preferLocal: false); } } } finally { // Abort all streams as connection has shutdown. lock (_streams) { foreach (var stream in _streams.Values) { stream.Abort(new ConnectionAbortedException("Connection is shutting down.")); } } ControlStream?.Abort(new ConnectionAbortedException("Connection is shutting down.")); EncoderStream?.Abort(new ConnectionAbortedException("Connection is shutting down.")); DecoderStream?.Abort(new ConnectionAbortedException("Connection is shutting down.")); await controlTask; await encoderTask; await decoderTask; } }
internal async Task InnerProcessStreamsAsync <TContext>(IHttpApplication <TContext> application) where TContext : notnull { // An endpoint MAY avoid creating an encoder stream if it's not going to // be used(for example if its encoder doesn't wish to use the dynamic // table, or if the maximum size of the dynamic table permitted by the // peer is zero). // An endpoint MAY avoid creating a decoder stream if its decoder sets // the maximum capacity of the dynamic table to zero. // Don't create Encoder and Decoder as they aren't used now. Exception?error = null; // TODO should we await the control stream task? var controlTask = CreateControlStream(application); _timeoutControl.SetTimeout(Limits.KeepAliveTimeout.Ticks, TimeoutReason.KeepAlive); try { while (_isClosed == 0) { // TODO implement way to unblock loop for one call to accept async to update state. // Use cts for now, update to custom awaitable or different solution in the future. var streamContext = await _multiplexedContext.AcceptAsync(); try { if (streamContext == null) { break; } var quicStreamFeature = streamContext.Features.Get <IStreamDirectionFeature>(); var streamIdFeature = streamContext.Features.Get <IStreamIdFeature>(); Debug.Assert(quicStreamFeature != null); Debug.Assert(streamIdFeature != null); var httpConnectionContext = new Http3StreamContext( streamContext.ConnectionId, protocols: default, connectionContext: null !, // TODO connection context is null here. Should we set it to anything? _context.ServiceContext, streamContext.Features, _context.MemoryPool, streamContext.LocalEndPoint as IPEndPoint, streamContext.RemoteEndPoint as IPEndPoint, streamContext.Transport, streamContext, _serverSettings); httpConnectionContext.TimeoutControl = _context.TimeoutControl; if (!quicStreamFeature.CanWrite) { // Unidirectional stream var stream = new Http3ControlStream <TContext>(application, this, httpConnectionContext); ThreadPool.UnsafeQueueUserWorkItem(stream, preferLocal: false); } else { var streamId = streamIdFeature.StreamId; HighestStreamId = streamId; var http3Stream = new Http3Stream <TContext>(application, this, httpConnectionContext); var stream = http3Stream; lock (_streams) { _activeRequestCount++; _streams[streamId] = http3Stream; } KestrelEventSource.Log.RequestQueuedStart(stream, AspNetCore.Http.HttpProtocol.Http3); ThreadPool.UnsafeQueueUserWorkItem(stream, preferLocal: false); } }
public async Task ProcessRequestsAsync <TContext>(IHttpApplication <TContext> application) where TContext : notnull { // An endpoint MAY avoid creating an encoder stream if it's not going to // be used(for example if its encoder doesn't wish to use the dynamic // table, or if the maximum size of the dynamic table permitted by the // peer is zero). // An endpoint MAY avoid creating a decoder stream if its decoder sets // the maximum capacity of the dynamic table to zero. // Don't create Encoder and Decoder as they aren't used now. Exception?error = null; ValueTask outboundControlStreamTask = default; try { var outboundControlStream = await CreateNewUnidirectionalStreamAsync(application); lock (_sync) { OutboundControlStream = outboundControlStream; } // Don't delay on waiting to send outbound control stream settings. outboundControlStreamTask = ProcessOutboundControlStreamAsync(outboundControlStream); while (_isClosed == 0) { // Don't pass a cancellation token to AcceptAsync. // AcceptAsync will return null if the connection is gracefully shutting down or aborted. var streamContext = await _multiplexedContext.AcceptAsync(); try { if (streamContext == null) { break; } var streamDirectionFeature = streamContext.Features.Get <IStreamDirectionFeature>(); var streamIdFeature = streamContext.Features.Get <IStreamIdFeature>(); Debug.Assert(streamDirectionFeature != null); Debug.Assert(streamIdFeature != null); if (!streamDirectionFeature.CanWrite) { // Unidirectional stream var stream = new Http3ControlStream <TContext>(application, CreateHttpStreamContext(streamContext)); _streamLifetimeHandler.OnStreamCreated(stream); ThreadPool.UnsafeQueueUserWorkItem(stream, preferLocal: false); } else { var persistentStateFeature = streamContext.Features.Get <IPersistentStateFeature>(); Debug.Assert(persistentStateFeature != null, $"Required {nameof(IPersistentStateFeature)} not on stream context."); // Request stream UpdateHighestStreamId(streamIdFeature.StreamId); Http3Stream <TContext> stream; // Check whether there is an existing HTTP/3 stream on the transport stream. // A stream will only be cached if the transport stream itself is reused. if (!persistentStateFeature.State.TryGetValue(StreamPersistentStateKey, out var s)) { stream = new Http3Stream <TContext>(application, CreateHttpStreamContext(streamContext)); persistentStateFeature.State.Add(StreamPersistentStateKey, stream); } else { stream = (Http3Stream <TContext>)s !; stream.InitializeWithExistingContext(streamContext.Transport); } _streamLifetimeHandler.OnStreamCreated(stream); KestrelEventSource.Log.RequestQueuedStart(stream, AspNetCore.Http.HttpProtocol.Http3); ThreadPool.UnsafeQueueUserWorkItem(stream, preferLocal: false); } } finally { UpdateConnectionState(); } } } catch (ConnectionResetException ex) { lock (_streams) { if (_activeRequestCount > 0) { Log.RequestProcessingError(_context.ConnectionId, ex); } } error = ex; } catch (IOException ex) { Log.RequestProcessingError(_context.ConnectionId, ex); error = ex; } catch (ConnectionAbortedException ex) { Log.RequestProcessingError(_context.ConnectionId, ex); error = ex; } catch (Http3ConnectionErrorException ex) { Log.Http3ConnectionError(_context.ConnectionId, ex); error = ex; } catch (Exception ex) { error = ex; } finally { var connectionError = error as ConnectionAbortedException ?? new ConnectionAbortedException(CoreStrings.Http3ConnectionFaulted, error !); try { if (TryClose()) { // This throws when connection is shut down. // TODO how to make it so we can distinguish between Abort from server vs client? await SendGoAway(GetHighestStreamId()); } foreach (var stream in _streams.Values) { stream.Abort(connectionError, (Http3ErrorCode)_errorCodeFeature.Error); } lock (_sync) { OutboundControlStream?.Abort(connectionError, (Http3ErrorCode)_errorCodeFeature.Error); } while (_activeRequestCount > 0) { await _streamCompletionAwaitable; } await outboundControlStreamTask; _context.TimeoutControl.CancelTimeout(); _context.TimeoutControl.StartDrainTimeout(Limits.MinResponseDataRate, Limits.MaxResponseBufferSize); } catch { Abort(connectionError, Http3ErrorCode.InternalError); throw; } } }
public async Task ProcessRequestsAsync <TContext>(IHttpApplication <TContext> application) where TContext : notnull { // An endpoint MAY avoid creating an encoder stream if it's not going to // be used(for example if its encoder doesn't wish to use the dynamic // table, or if the maximum size of the dynamic table permitted by the // peer is zero). // An endpoint MAY avoid creating a decoder stream if its decoder sets // the maximum capacity of the dynamic table to zero. // Don't create Encoder and Decoder as they aren't used now. Exception?error = null; ValueTask outboundControlStreamTask = default; try { var outboundControlStream = await CreateNewUnidirectionalStreamAsync(application); lock (_sync) { OutboundControlStream = outboundControlStream; } // Don't delay on waiting to send outbound control stream settings. outboundControlStreamTask = ProcessOutboundControlStreamAsync(outboundControlStream); while (_isClosed == 0) { // Don't pass a cancellation token to AcceptAsync. // AcceptAsync will return null if the connection is gracefully shutting down or aborted. var streamContext = await _multiplexedContext.AcceptAsync(); try { if (streamContext == null) { break; } var streamDirectionFeature = streamContext.Features.Get <IStreamDirectionFeature>(); var streamIdFeature = streamContext.Features.Get <IStreamIdFeature>(); Debug.Assert(streamDirectionFeature != null); Debug.Assert(streamIdFeature != null); var httpConnectionContext = new Http3StreamContext( streamContext.ConnectionId, protocols: default, connectionContext: null !, // TODO connection context is null here. Should we set it to anything? _context.ServiceContext, streamContext.Features, _context.MemoryPool, streamContext.LocalEndPoint as IPEndPoint, streamContext.RemoteEndPoint as IPEndPoint, streamContext.Transport, _streamLifetimeHandler, streamContext, _clientSettings, _serverSettings); httpConnectionContext.TimeoutControl = _context.TimeoutControl; if (!streamDirectionFeature.CanWrite) { // Unidirectional stream var stream = new Http3ControlStream <TContext>(application, httpConnectionContext); _streamLifetimeHandler.OnStreamCreated(stream); ThreadPool.UnsafeQueueUserWorkItem(stream, preferLocal: false); } else { // Request stream UpdateHighestStreamId(streamIdFeature.StreamId); var stream = new Http3Stream <TContext>(application, httpConnectionContext); _streamLifetimeHandler.OnStreamCreated(stream); KestrelEventSource.Log.RequestQueuedStart(stream, AspNetCore.Http.HttpProtocol.Http3); ThreadPool.UnsafeQueueUserWorkItem(stream, preferLocal: false); } }