Skip to content

Commit 01c18f2

Browse files
committed
Improve control stream handling with HTTP/3 (#26644)
1 parent 31e855d commit 01c18f2

File tree

2 files changed

+98
-67
lines changed

2 files changed

+98
-67
lines changed

src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs

Lines changed: 64 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,21 @@
55
using System.Collections.Generic;
66
using System.Diagnostics;
77
using System.Net;
8-
using System.Net.Http;
98
using System.Threading;
109
using System.Threading.Tasks;
1110
using Microsoft.AspNetCore.Connections;
1211
using Microsoft.AspNetCore.Connections.Experimental;
1312
using Microsoft.AspNetCore.Connections.Features;
1413
using Microsoft.AspNetCore.Hosting.Server;
1514
using Microsoft.AspNetCore.Http.Features;
16-
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3.QPack;
1715
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
1816
using Microsoft.Extensions.Logging;
1917

2018
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
2119
{
2220
internal class Http3Connection : ITimeoutHandler
2321
{
24-
public DynamicTable DynamicTable { get; set; }
22+
public Http3ControlStream OutboundControlStream { get; set; }
2523

2624
public Http3ControlStream? ControlStream { get; set; }
2725
public Http3ControlStream? EncoderStream { get; set; }
@@ -47,7 +45,6 @@ public Http3Connection(Http3ConnectionContext context)
4745
{
4846
_multiplexedContext = context.ConnectionContext;
4947
_context = context;
50-
DynamicTable = new DynamicTable(0);
5148
_systemClock = context.ServiceContext.SystemClock;
5249
_timeoutControl = new TimeoutControl(this);
5350
_context.TimeoutControl ??= _timeoutControl;
@@ -198,10 +195,16 @@ public void OnTimeout(TimeoutReason reason)
198195

199196
internal async Task InnerProcessRequestsAsync<TContext>(IHttpApplication<TContext> application) where TContext : notnull
200197
{
201-
// Start other three unidirectional streams here.
198+
// An endpoint MAY avoid creating an encoder stream if it's not going to
199+
// be used(for example if its encoder doesn't wish to use the dynamic
200+
// table, or if the maximum size of the dynamic table permitted by the
201+
// peer is zero).
202+
203+
// An endpoint MAY avoid creating a decoder stream if its decoder sets
204+
// the maximum capacity of the dynamic table to zero.
205+
206+
// Don't create Encoder and Decoder as they aren't used now.
202207
var controlTask = CreateControlStream(application);
203-
var encoderTask = CreateEncoderStream(application);
204-
var decoderTask = CreateDecoderStream(application);
205208

206209
try
207210
{
@@ -271,39 +274,33 @@ internal async Task InnerProcessRequestsAsync<TContext>(IHttpApplication<TContex
271274
stream.Abort(_abortedException);
272275
}
273276
}
277+
else
278+
{
279+
foreach (var stream in _streams.Values)
280+
{
281+
stream.OnInputOrOutputCompleted();
282+
}
283+
}
284+
285+
_inboundControlStream?.OnInputOrOutputCompleted();
286+
_inboundEncoderStream?.OnInputOrOutputCompleted();
287+
_inboundDecoderStream?.OnInputOrOutputCompleted();
274288
}
275289

276-
ControlStream?.Abort(new ConnectionAbortedException("Connection is shutting down."));
277-
EncoderStream?.Abort(new ConnectionAbortedException("Connection is shutting down."));
278-
DecoderStream?.Abort(new ConnectionAbortedException("Connection is shutting down."));
290+
OutboundControlStream?.Abort(new ConnectionAbortedException("Connection is shutting down."));
279291

280292
await controlTask;
281-
await encoderTask;
282-
await decoderTask;
283293
}
284294
}
285295

286296
private async ValueTask CreateControlStream<TContext>(IHttpApplication<TContext> application) where TContext : notnull
287297
{
288298
var stream = await CreateNewUnidirectionalStreamAsync(application);
289-
ControlStream = stream;
299+
OutboundControlStream = stream;
290300
await stream.SendStreamIdAsync(id: 0);
291301
await stream.SendSettingsFrameAsync();
292302
}
293303

294-
private async ValueTask CreateEncoderStream<TContext>(IHttpApplication<TContext> application) where TContext : notnull
295-
{
296-
var stream = await CreateNewUnidirectionalStreamAsync(application);
297-
EncoderStream = stream;
298-
await stream.SendStreamIdAsync(id: 2);
299-
}
300-
301-
private async ValueTask CreateDecoderStream<TContext>(IHttpApplication<TContext> application) where TContext : notnull
302-
{
303-
var stream = await CreateNewUnidirectionalStreamAsync(application);
304-
DecoderStream = stream;
305-
await stream.SendStreamIdAsync(id: 3);
306-
}
307304

308305
private async ValueTask<Http3ControlStream> CreateNewUnidirectionalStreamAsync<TContext>(IHttpApplication<TContext> application) where TContext : notnull
309306
{
@@ -350,10 +347,10 @@ private void InnerAbort(ConnectionAbortedException ex)
350347

351348
lock (_sync)
352349
{
353-
if (ControlStream != null)
350+
if (OutboundControlStream != null)
354351
{
355352
// TODO need to await this somewhere or allow this to be called elsewhere?
356-
ControlStream.SendGoAway(_highestOpenedStreamId).GetAwaiter().GetResult();
353+
OutboundControlStream.SendGoAway(_highestOpenedStreamId).GetAwaiter().GetResult();
357354
}
358355

359356
_haveSentGoAway = true;
@@ -383,5 +380,44 @@ internal void RemoveStream(long streamId)
383380
_streams.Remove(streamId);
384381
}
385382
}
383+
384+
public bool SetInboundControlStream(Http3ControlStream stream)
385+
{
386+
lock (_sync)
387+
{
388+
if (_inboundControlStream == null)
389+
{
390+
_inboundControlStream = stream;
391+
return true;
392+
}
393+
return false;
394+
}
395+
}
396+
397+
public bool SetInboundEncoderStream(Http3ControlStream stream)
398+
{
399+
lock (_sync)
400+
{
401+
if (_inboundEncoderStream == null)
402+
{
403+
_inboundEncoderStream = stream;
404+
return true;
405+
}
406+
return false;
407+
}
408+
}
409+
410+
public bool SetInboundDecoderStream(Http3ControlStream stream)
411+
{
412+
lock (_sync)
413+
{
414+
if (_inboundDecoderStream == null)
415+
{
416+
_inboundDecoderStream = stream;
417+
return true;
418+
}
419+
return false;
420+
}
421+
}
386422
}
387423
}

src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStream.cs

Lines changed: 34 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -82,46 +82,14 @@ private bool TryClose()
8282
{
8383
if (Interlocked.Exchange(ref _isClosed, 1) == 0)
8484
{
85+
Input.Complete();
8586
return true;
8687
}
8788

8889
// TODO make this actually close the Http3Stream by telling quic to close the stream.
8990
return false;
9091
}
9192

92-
private async ValueTask HandleEncodingTask()
93-
{
94-
var encoder = new EncoderStreamReader(10000); // TODO get value from limits
95-
while (_isClosed == 0)
96-
{
97-
var result = await Input.ReadAsync();
98-
var readableBuffer = result.Buffer;
99-
if (!readableBuffer.IsEmpty)
100-
{
101-
// This should always read all bytes in the input no matter what.
102-
encoder.Read(readableBuffer);
103-
}
104-
Input.AdvanceTo(readableBuffer.End);
105-
}
106-
}
107-
108-
private async ValueTask HandleDecodingTask()
109-
{
110-
var decoder = new DecoderStreamReader();
111-
while (_isClosed == 0)
112-
{
113-
var result = await Input.ReadAsync();
114-
var readableBuffer = result.Buffer;
115-
var consumed = readableBuffer.Start;
116-
var examined = readableBuffer.Start;
117-
if (!readableBuffer.IsEmpty)
118-
{
119-
decoder.Read(readableBuffer);
120-
}
121-
Input.AdvanceTo(readableBuffer.End);
122-
}
123-
}
124-
12593
internal async ValueTask SendStreamIdAsync(long id)
12694
{
12795
await _frameWriter.WriteStreamIdAsync(id);
@@ -182,25 +150,26 @@ public async Task ProcessRequestAsync<TContext>(IHttpApplication<TContext> appli
182150

183151
if (streamType == ControlStream)
184152
{
185-
if (_http3Connection.ControlStream != null)
153+
if (!_http3Connection.SetInboundControlStream(this))
186154
{
155+
// TODO propagate these errors to connection.
187156
throw new Http3ConnectionException("HTTP_STREAM_CREATION_ERROR");
188157
}
189158

190159
await HandleControlStream();
191160
}
192161
else if (streamType == EncoderStream)
193162
{
194-
if (_http3Connection.EncoderStream != null)
163+
if (!_http3Connection.SetInboundEncoderStream(this))
195164
{
196165
throw new Http3ConnectionException("HTTP_STREAM_CREATION_ERROR");
197166
}
167+
198168
await HandleEncodingTask();
199-
return;
200169
}
201170
else if (streamType == DecoderStream)
202171
{
203-
if (_http3Connection.DecoderStream != null)
172+
if (!_http3Connection.SetInboundDecoderStream(this))
204173
{
205174
throw new Http3ConnectionException("HTTP_STREAM_CREATION_ERROR");
206175
}
@@ -210,7 +179,6 @@ public async Task ProcessRequestAsync<TContext>(IHttpApplication<TContext> appli
210179
{
211180
// TODO Close the control stream as it's unexpected.
212181
}
213-
return;
214182
}
215183

216184
private async Task HandleControlStream()
@@ -250,6 +218,33 @@ private async Task HandleControlStream()
250218
}
251219
}
252220

221+
private async ValueTask HandleEncodingTask()
222+
{
223+
// Noop encoding task. Settings make it so we don't need to read content of encoder and decoder.
224+
// An endpoint MUST allow its peer to create an encoder stream and a
225+
// decoder stream even if the connection's settings prevent their use.
226+
227+
while (_isClosed == 0)
228+
{
229+
var result = await Input.ReadAsync();
230+
var readableBuffer = result.Buffer;
231+
Input.AdvanceTo(readableBuffer.End);
232+
}
233+
}
234+
235+
private async ValueTask HandleDecodingTask()
236+
{
237+
// Noop encoding task. Settings make it so we don't need to read content of encoder and decoder.
238+
// An endpoint MUST allow its peer to create an encoder stream and a
239+
// decoder stream even if the connection's settings prevent their use.
240+
while (_isClosed == 0)
241+
{
242+
var result = await Input.ReadAsync();
243+
var readableBuffer = result.Buffer;
244+
Input.AdvanceTo(readableBuffer.End);
245+
}
246+
}
247+
253248
private ValueTask ProcessHttp3ControlStream(in ReadOnlySequence<byte> payload)
254249
{
255250
// Two things:
@@ -283,7 +278,7 @@ private ValueTask ProcessSettingsFrameAsync(ReadOnlySequence<byte> payload)
283278
}
284279

285280
_haveReceivedSettingsFrame = true;
286-
using var closedRegistration = _context.ConnectionContext.ConnectionClosed.Register(state => ((Http3ControlStream)state!).OnStreamClosed(), this);
281+
using var closedRegistration = _context.StreamContext.ConnectionClosed.Register(state => ((Http3ControlStream)state!).OnStreamClosed(), this);
287282

288283
while (true)
289284
{

0 commit comments

Comments
 (0)