Skip to content

Commit fd65bd8

Browse files
authored
HTTP/3: Fix flakey pooling test (#35168)
1 parent 83927d9 commit fd65bd8

File tree

4 files changed

+93
-28
lines changed

4 files changed

+93
-28
lines changed

src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,8 @@ internal void VerifyGoAway(Http3FrameWithPayload frame, long expectedLastStreamI
200200

201201
public void AdvanceClock(TimeSpan timeSpan)
202202
{
203+
Logger.LogDebug($"Advancing clock {timeSpan}.");
204+
203205
var clock = _mockSystemClock;
204206
var endTime = clock.UtcNow + timeSpan;
205207

@@ -221,10 +223,13 @@ public void TriggerTick(DateTimeOffset now)
221223

222224
public async Task InitializeConnectionAsync(RequestDelegate application)
223225
{
224-
MultiplexedConnectionContext = new TestMultiplexedConnectionContext(this);
226+
MultiplexedConnectionContext = new TestMultiplexedConnectionContext(this)
227+
{
228+
ConnectionId = "TEST"
229+
};
225230

226231
var httpConnectionContext = new HttpMultiplexedConnectionContext(
227-
connectionId: "TestConnectionId",
232+
connectionId: MultiplexedConnectionContext.ConnectionId,
228233
HttpProtocols.Http3,
229234
altSvcHeader: null,
230235
connectionContext: MultiplexedConnectionContext,
@@ -398,11 +403,16 @@ internal async ValueTask<Http3ControlStream> CreateControlStream(int? id)
398403

399404
internal ValueTask<Http3RequestStream> CreateRequestStream(Http3RequestHeaderHandler headerHandler = null)
400405
{
406+
var requestStreamId = GetStreamId(0x00);
401407
if (!_streamContextPool.TryDequeue(out var testStreamContext))
402408
{
403409
testStreamContext = new TestStreamContext(canRead: true, canWrite: true, this);
404410
}
405-
testStreamContext.Initialize(GetStreamId(0x00));
411+
else
412+
{
413+
Logger.LogDebug($"Reusing context for request stream {requestStreamId}.");
414+
}
415+
testStreamContext.Initialize(requestStreamId);
406416

407417
var stream = new Http3RequestStream(this, Connection, testStreamContext, headerHandler ?? new Http3RequestHeaderHandler());
408418
_runningStreams[stream.StreamId] = stream;
@@ -1102,22 +1112,29 @@ public override void Abort(ConnectionAbortedException abortReason)
11021112

11031113
public override ValueTask DisposeAsync()
11041114
{
1105-
_testBase.Logger.LogInformation($"Disposing stream {StreamId}");
1106-
1107-
Disposed = true;
1108-
_disposedTcs.TrySetResult();
1115+
_testBase.Logger.LogDebug($"Disposing stream {StreamId}");
11091116

1117+
var readerCompletedSuccessfully = _transportPipeReader.IsCompletedSuccessfully;
1118+
var writerCompletedSuccessfully = _transportPipeWriter.IsCompletedSuccessfully;
11101119
var canReuse = !_isAborted &&
1111-
_transportPipeReader.IsCompletedSuccessfully &&
1112-
_transportPipeWriter.IsCompletedSuccessfully;
1120+
readerCompletedSuccessfully &&
1121+
writerCompletedSuccessfully;
11131122

11141123
_pair.Transport.Input.Complete();
11151124
_pair.Transport.Output.Complete();
11161125

11171126
if (canReuse)
11181127
{
1128+
_testBase.Logger.LogDebug($"Pooling stream {StreamId} for reuse.");
11191129
_testBase._streamContextPool.Enqueue(this);
11201130
}
1131+
else
1132+
{
1133+
_testBase.Logger.LogDebug($"Can't reuse stream {StreamId}. Aborted: {_isAborted}, Reader completed successfully: {readerCompletedSuccessfully}, Writer completed successfully: {writerCompletedSuccessfully}.");
1134+
}
1135+
1136+
Disposed = true;
1137+
_disposedTcs.TrySetResult();
11211138

11221139
return ValueTask.CompletedTask;
11231140
}

src/Servers/Kestrel/shared/test/TestContextFactory.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,10 @@ public static HttpMultiplexedConnectionContext CreateHttp3ConnectionContext(
8484
ITimeoutControl timeoutControl = null)
8585
{
8686
var http3ConnectionContext = new HttpMultiplexedConnectionContext(
87-
"TestConnectionId",
87+
"TEST",
8888
HttpProtocols.Http3,
8989
altSvcHeader: null,
90-
connectionContext ?? new TestMultiplexedConnectionContext(),
90+
connectionContext ?? new TestMultiplexedConnectionContext { ConnectionId = "TEST" },
9191
serviceContext ?? CreateServiceContext(new KestrelServerOptions()),
9292
connectionFeatures ?? new FeatureCollection(),
9393
memoryPool ?? PinnedBlockMemoryPoolFactory.Create(),

src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3ConnectionTests.cs

Lines changed: 62 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using Microsoft.AspNetCore.Http.Features;
1313
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3;
1414
using Microsoft.AspNetCore.Testing;
15+
using Microsoft.Extensions.Logging;
1516
using Microsoft.Net.Http.Headers;
1617
using Xunit;
1718
using Http3SettingType = Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3.Http3SettingType;
@@ -90,6 +91,8 @@ public async Task GOAWAY_GracefulServerShutdown_SendsGoAway(int connectionReques
9091
await request.SendHeadersAsync(Headers);
9192
await request.EndStreamAsync();
9293
await request.ExpectReceiveEndOfStream();
94+
95+
await request.OnStreamCompletedTask.DefaultTimeout();
9396
}
9497

9598
// Trigger server shutdown.
@@ -272,16 +275,16 @@ public async Task StreamPool_MultipleStreamsInSequence_PooledStreamReused()
272275

273276
await Http3Api.InitializeConnectionAsync(_echoApplication);
274277

275-
var streamContext1 = await MakeRequestAsync(0, headers);
276-
var streamContext2 = await MakeRequestAsync(1, headers);
278+
var streamContext1 = await MakeRequestAsync(0, headers, sendData: true, waitForServerDispose: true);
279+
var streamContext2 = await MakeRequestAsync(1, headers, sendData: true, waitForServerDispose: true);
277280

278281
Assert.Same(streamContext1, streamContext2);
279282
}
280283

281284
[Theory]
282285
[InlineData(10)]
283286
[InlineData(100)]
284-
[InlineData(1000)]
287+
[InlineData(500)]
285288
[QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/34685")]
286289
public async Task StreamPool_VariableMultipleStreamsInSequence_PooledStreamReused(int count)
287290
{
@@ -299,40 +302,82 @@ public async Task StreamPool_VariableMultipleStreamsInSequence_PooledStreamReuse
299302
ConnectionContext last = null;
300303
for (var i = 0; i < count; i++)
301304
{
302-
var streamContext = await MakeRequestAsync(i, headers);
305+
Logger.LogInformation($"Iteration {i}");
306+
307+
var streamContext = await MakeRequestAsync(i, headers, sendData: true, waitForServerDispose: true);
303308

304309
first ??= streamContext;
305310
last = streamContext;
311+
312+
Assert.Same(first, last);
306313
}
314+
}
315+
316+
[Theory]
317+
[InlineData(10, false)]
318+
[InlineData(10, true)]
319+
[InlineData(100, false)]
320+
[InlineData(100, true)]
321+
[InlineData(500, false)]
322+
[InlineData(500, true)]
323+
[QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/34685")]
324+
public async Task VariableMultipleStreamsInSequence_Success(int count, bool sendData)
325+
{
326+
var headers = new[]
327+
{
328+
new KeyValuePair<string, string>(HeaderNames.Method, "Custom"),
329+
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
330+
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
331+
new KeyValuePair<string, string>(HeaderNames.Authority, "localhost:80"),
332+
};
333+
334+
var requestDelegate = sendData ? _echoApplication : _noopApplication;
307335

308-
Assert.Same(first, last);
336+
await Http3Api.InitializeConnectionAsync(requestDelegate);
337+
338+
for (var i = 0; i < count; i++)
339+
{
340+
Logger.LogInformation($"Iteration {i}");
341+
342+
await MakeRequestAsync(i, headers, sendData, waitForServerDispose: false);
343+
}
309344
}
310345

311-
private async Task<ConnectionContext> MakeRequestAsync(int index, KeyValuePair<string, string>[] headers)
346+
private async Task<ConnectionContext> MakeRequestAsync(int index, KeyValuePair<string, string>[] headers, bool sendData, bool waitForServerDispose)
312347
{
313348
var requestStream = await Http3Api.CreateRequestStream();
314349
var streamContext = requestStream.StreamContext;
315350

316-
await requestStream.SendHeadersAsync(headers);
351+
await requestStream.SendHeadersAsync(headers, endStream: !sendData);
317352

318-
await requestStream.SendDataAsync(Encoding.ASCII.GetBytes($"Hello world {index}"));
353+
if (sendData)
354+
{
355+
await requestStream.SendDataAsync(Encoding.ASCII.GetBytes($"Hello world {index}"));
356+
}
319357

320358
await requestStream.ExpectHeadersAsync();
321-
var responseData = await requestStream.ExpectDataAsync();
322-
Assert.Equal($"Hello world {index}", Encoding.ASCII.GetString(responseData.ToArray()));
323359

324-
Assert.False(requestStream.Disposed, "Request is in progress and shouldn't be disposed.");
360+
if (sendData)
361+
{
362+
var responseData = await requestStream.ExpectDataAsync();
363+
Assert.Equal($"Hello world {index}", Encoding.ASCII.GetString(responseData.ToArray()));
364+
365+
Assert.False(requestStream.Disposed, "Request is in progress and shouldn't be disposed.");
325366

326-
await requestStream.SendDataAsync(Encoding.ASCII.GetBytes($"End {index}"), endStream: true);
327-
responseData = await requestStream.ExpectDataAsync();
328-
Assert.Equal($"End {index}", Encoding.ASCII.GetString(responseData.ToArray()));
367+
await requestStream.SendDataAsync(Encoding.ASCII.GetBytes($"End {index}"), endStream: true);
368+
responseData = await requestStream.ExpectDataAsync();
369+
Assert.Equal($"End {index}", Encoding.ASCII.GetString(responseData.ToArray()));
370+
}
329371

330372
await requestStream.ExpectReceiveEndOfStream();
331373

332-
await requestStream.OnStreamCompletedTask.DefaultTimeout();
374+
if (waitForServerDispose)
375+
{
376+
await requestStream.OnDisposedTask.DefaultTimeout();
377+
Assert.True(requestStream.Disposed, "Request is complete and should be disposed.");
333378

334-
await requestStream.OnDisposedTask.DefaultTimeout();
335-
Assert.True(requestStream.Disposed, "Request is complete and should be disposed.");
379+
Logger.LogInformation($"Received notification that stream {index} disposed.");
380+
}
336381

337382
return streamContext;
338383
}

src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TimeoutTests.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3;
1313
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
1414
using Microsoft.AspNetCore.Testing;
15+
using Microsoft.Extensions.Logging;
1516
using Microsoft.Net.Http.Headers;
1617
using Moq;
1718
using Xunit;
@@ -502,6 +503,7 @@ public async Task DATA_Received_TooSlowlyOnSecondStream_AbortsConnectionAfterNon
502503
var inboundControlStream = await Http3Api.GetInboundControlStream();
503504
await inboundControlStream.ExpectSettingsAsync();
504505

506+
Logger.LogInformation("Sending first request");
505507
var requestStream1 = await Http3Api.CreateRequestStream();
506508

507509
// _maxData is 16 KiB, and 16 KiB / 240 bytes/sec ~= 68 secs which is far above the grace period.
@@ -513,6 +515,7 @@ public async Task DATA_Received_TooSlowlyOnSecondStream_AbortsConnectionAfterNon
513515

514516
await requestStream1.ExpectReceiveEndOfStream();
515517

518+
Logger.LogInformation("Sending second request");
516519
var requestStream2 = await Http3Api.CreateRequestStream();
517520

518521
await requestStream2.SendHeadersAsync(ReadRateRequestHeaders(_maxData.Length), endStream: false);

0 commit comments

Comments
 (0)