Skip to content

Commit 9948eb6

Browse files
jkotalikJamesNK
authored andcommitted
Improve control stream handling with HTTP/3 (#26644)
1 parent 1233854 commit 9948eb6

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
{
@@ -270,39 +273,33 @@ internal async Task InnerProcessRequestsAsync<TContext>(IHttpApplication<TContex
270273
stream.Abort(_abortedException);
271274
}
272275
}
276+
else
277+
{
278+
foreach (var stream in _streams.Values)
279+
{
280+
stream.OnInputOrOutputCompleted();
281+
}
282+
}
283+
284+
_inboundControlStream?.OnInputOrOutputCompleted();
285+
_inboundEncoderStream?.OnInputOrOutputCompleted();
286+
_inboundDecoderStream?.OnInputOrOutputCompleted();
273287
}
274288

275-
ControlStream?.Abort(new ConnectionAbortedException("Connection is shutting down."));
276-
EncoderStream?.Abort(new ConnectionAbortedException("Connection is shutting down."));
277-
DecoderStream?.Abort(new ConnectionAbortedException("Connection is shutting down."));
289+
OutboundControlStream?.Abort(new ConnectionAbortedException("Connection is shutting down."));
278290

279291
await controlTask;
280-
await encoderTask;
281-
await decoderTask;
282292
}
283293
}
284294

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

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

307304
private async ValueTask<Http3ControlStream> CreateNewUnidirectionalStreamAsync<TContext>(IHttpApplication<TContext> application) where TContext : notnull
308305
{
@@ -349,10 +346,10 @@ private void InnerAbort(ConnectionAbortedException ex)
349346

350347
lock (_sync)
351348
{
352-
if (ControlStream != null)
349+
if (OutboundControlStream != null)
353350
{
354351
// TODO need to await this somewhere or allow this to be called elsewhere?
355-
ControlStream.SendGoAway(_highestOpenedStreamId).GetAwaiter().GetResult();
352+
OutboundControlStream.SendGoAway(_highestOpenedStreamId).GetAwaiter().GetResult();
356353
}
357354

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

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)