Skip to content

Commit 4faa91c

Browse files
authored
Remove QuicTransportOptions.IdleTimeout #34955 (#43250)
1 parent ac4bd5e commit 4faa91c

File tree

6 files changed

+122
-17
lines changed

6 files changed

+122
-17
lines changed

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

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ private void UpdateHighestOpenedRequestStreamId(long streamId)
9797
public Http3ControlStream? EncoderStream { get; set; }
9898
public Http3ControlStream? DecoderStream { get; set; }
9999
public string ConnectionId => _context.ConnectionId;
100+
public ITimeoutControl TimeoutControl => _context.TimeoutControl;
100101

101102
public void StopProcessingNextRequest()
102103
=> StopProcessingNextRequest(serverInitiated: true);
@@ -304,7 +305,7 @@ private void UpdateStreamTimeouts(DateTimeOffset now)
304305

305306
if (stream.StreamTimeoutTicks == default)
306307
{
307-
stream.StreamTimeoutTicks = _context.TimeoutControl.GetResponseDrainDeadline(ticks, minDataRate);
308+
stream.StreamTimeoutTicks = TimeoutControl.GetResponseDrainDeadline(ticks, minDataRate);
308309
}
309310

310311
if (stream.StreamTimeoutTicks < ticks)
@@ -346,6 +347,9 @@ public async Task ProcessRequestsAsync<TContext>(IHttpApplication<TContext> appl
346347
// Don't delay on waiting to send outbound control stream settings.
347348
outboundControlStreamTask = ProcessOutboundControlStreamAsync(outboundControlStream);
348349

350+
// Close the connection if we don't receive any request streams
351+
TimeoutControl.SetTimeout(Limits.KeepAliveTimeout.Ticks, TimeoutReason.KeepAlive);
352+
349353
while (_stoppedAcceptingStreams == 0)
350354
{
351355
var streamContext = await _multiplexedContext.AcceptAsync(_acceptStreamsCts.Token);
@@ -534,7 +538,7 @@ public async Task ProcessRequestsAsync<TContext>(IHttpApplication<TContext> appl
534538
await _streamCompletionAwaitable;
535539
}
536540

537-
_context.TimeoutControl.CancelTimeout();
541+
TimeoutControl.CancelTimeout();
538542
}
539543
catch
540544
{
@@ -793,6 +797,11 @@ void IHttp3StreamLifetimeHandler.OnStreamCreated(IHttp3Stream stream)
793797
{
794798
if (stream.IsRequestStream)
795799
{
800+
if (_activeRequestCount == 0 && TimeoutControl.TimerReason == TimeoutReason.KeepAlive)
801+
{
802+
TimeoutControl.CancelTimeout();
803+
}
804+
796805
_activeRequestCount++;
797806
}
798807
_streams[stream.StreamId] = stream;
@@ -806,6 +815,11 @@ void IHttp3StreamLifetimeHandler.OnStreamCompleted(IHttp3Stream stream)
806815
if (stream.IsRequestStream)
807816
{
808817
_activeRequestCount--;
818+
819+
if (_activeRequestCount == 0)
820+
{
821+
TimeoutControl.SetTimeout(Limits.KeepAliveTimeout.Ticks, TimeoutReason.KeepAlive);
822+
}
809823
}
810824
_streams.Remove(stream.StreamId);
811825
}

src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionListener.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ public QuicConnectionListener(
8888
var connectionOptions = new QuicServerConnectionOptions
8989
{
9090
ServerAuthenticationOptions = serverAuthenticationOptions,
91-
IdleTimeout = options.IdleTimeout,
91+
IdleTimeout = Timeout.InfiniteTimeSpan, // Kestrel manages connection lifetimes itself so it can send GoAway's.
9292
MaxInboundBidirectionalStreams = options.MaxBidirectionalStreamCount,
9393
MaxInboundUnidirectionalStreams = options.MaxUnidirectionalStreamCount,
9494
DefaultCloseErrorCode = options.DefaultCloseErrorCode,

src/Servers/Kestrel/Transport.Quic/src/PublicAPI.Unshipped.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,7 @@ Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.QuicTransportOptions.DefaultS
55
Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.QuicTransportOptions.DefaultStreamErrorCode.set -> void
66
Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.QuicTransportOptions.MaxBidirectionalStreamCount.get -> int
77
Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.QuicTransportOptions.MaxUnidirectionalStreamCount.get -> int
8+
*REMOVED*Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.QuicTransportOptions.IdleTimeout.get -> System.TimeSpan
9+
*REMOVED*Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.QuicTransportOptions.IdleTimeout.set -> void
810
*REMOVED*Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.QuicTransportOptions.MaxBidirectionalStreamCount.get -> ushort
911
*REMOVED*Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.QuicTransportOptions.MaxUnidirectionalStreamCount.get -> ushort

src/Servers/Kestrel/Transport.Quic/src/QuicTransportOptions.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,6 @@ public sealed class QuicTransportOptions
2323
/// </summary>
2424
public int MaxUnidirectionalStreamCount { get; set; } = 10;
2525

26-
/// <summary>
27-
/// Sets the idle timeout for connections and streams.
28-
/// </summary>
29-
public TimeSpan IdleTimeout { get; set; } = TimeSpan.FromSeconds(130); // Matches KestrelServerLimits.KeepAliveTimeout.
30-
3126
/// <summary>
3227
/// The maximum read size.
3328
/// </summary>

src/Servers/Kestrel/Transport.Quic/test/QuicTestHelpers.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ public static QuicTransportFactory CreateTransportFactory(
3333
long defaultCloseErrorCode = 0)
3434
{
3535
var quicTransportOptions = new QuicTransportOptions();
36-
quicTransportOptions.IdleTimeout = TimeSpan.FromMinutes(1);
3736
quicTransportOptions.MaxBidirectionalStreamCount = 200;
3837
quicTransportOptions.MaxUnidirectionalStreamCount = 200;
3938
quicTransportOptions.DefaultCloseErrorCode = defaultCloseErrorCode;

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

Lines changed: 103 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,122 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System;
5-
using System.Collections.Generic;
6-
using System.Linq;
74
using System.Net.Http;
8-
using System.Reflection.PortableExecutable;
9-
using System.Threading.Tasks;
105
using Microsoft.AspNetCore.Connections;
116
using Microsoft.AspNetCore.Http;
127
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
13-
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3;
148
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
159
using Microsoft.AspNetCore.Testing;
1610
using Microsoft.Extensions.Logging;
1711
using Moq;
18-
using Xunit;
19-
using Xunit.Sdk;
2012

2113
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests;
2214

2315
public class Http3TimeoutTests : Http3TestBase
2416
{
17+
[Fact]
18+
public async Task KeepAliveTimeout_ControlStreamNotReceived_ConnectionClosed()
19+
{
20+
var limits = _serviceContext.ServerOptions.Limits;
21+
22+
await Http3Api.InitializeConnectionAsync(_noopApplication).DefaultTimeout();
23+
24+
var controlStream = await Http3Api.GetInboundControlStream().DefaultTimeout();
25+
await controlStream.ExpectSettingsAsync().DefaultTimeout();
26+
27+
Http3Api.AdvanceClock(limits.KeepAliveTimeout + TimeSpan.FromTicks(1));
28+
29+
await Http3Api.WaitForConnectionStopAsync(0, false, expectedErrorCode: Http3ErrorCode.NoError);
30+
}
31+
32+
[Fact]
33+
public async Task KeepAliveTimeout_RequestNotReceived_ConnectionClosed()
34+
{
35+
var limits = _serviceContext.ServerOptions.Limits;
36+
37+
await Http3Api.InitializeConnectionAsync(_noopApplication).DefaultTimeout();
38+
await Http3Api.CreateControlStream();
39+
40+
var controlStream = await Http3Api.GetInboundControlStream().DefaultTimeout();
41+
await controlStream.ExpectSettingsAsync().DefaultTimeout();
42+
43+
Http3Api.AdvanceClock(limits.KeepAliveTimeout + TimeSpan.FromTicks(1));
44+
45+
await Http3Api.WaitForConnectionStopAsync(0, false, expectedErrorCode: Http3ErrorCode.NoError);
46+
}
47+
48+
[Fact]
49+
public async Task KeepAliveTimeout_AfterRequestComplete_ConnectionClosed()
50+
{
51+
var requestHeaders = new[]
52+
{
53+
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
54+
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
55+
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
56+
};
57+
58+
var limits = _serviceContext.ServerOptions.Limits;
59+
60+
await Http3Api.InitializeConnectionAsync(_noopApplication).DefaultTimeout();
61+
62+
await Http3Api.CreateControlStream();
63+
var controlStream = await Http3Api.GetInboundControlStream().DefaultTimeout();
64+
await controlStream.ExpectSettingsAsync().DefaultTimeout();
65+
var requestStream = await Http3Api.CreateRequestStream(requestHeaders, endStream: true);
66+
await requestStream.ExpectHeadersAsync();
67+
68+
await requestStream.ExpectReceiveEndOfStream();
69+
await requestStream.OnDisposedTask.DefaultTimeout();
70+
71+
Http3Api.AdvanceClock(limits.KeepAliveTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1));
72+
73+
await Http3Api.WaitForConnectionStopAsync(4, false, expectedErrorCode: Http3ErrorCode.NoError);
74+
}
75+
76+
[Fact]
77+
public async Task KeepAliveTimeout_LongRunningRequest_KeepsConnectionAlive()
78+
{
79+
var requestHeaders = new[]
80+
{
81+
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
82+
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
83+
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
84+
};
85+
86+
var limits = _serviceContext.ServerOptions.Limits;
87+
var requestReceivedTcs = new TaskCompletionSource();
88+
var requestFinishedTcs = new TaskCompletionSource();
89+
90+
await Http3Api.InitializeConnectionAsync(_ =>
91+
{
92+
requestReceivedTcs.SetResult();
93+
return requestFinishedTcs.Task;
94+
}).DefaultTimeout();
95+
96+
await Http3Api.CreateControlStream();
97+
var controlStream = await Http3Api.GetInboundControlStream().DefaultTimeout();
98+
await controlStream.ExpectSettingsAsync().DefaultTimeout();
99+
var requestStream = await Http3Api.CreateRequestStream(requestHeaders, endStream: true);
100+
101+
await requestReceivedTcs.Task;
102+
103+
Http3Api.AdvanceClock(limits.KeepAliveTimeout);
104+
Http3Api.AdvanceClock(limits.KeepAliveTimeout);
105+
Http3Api.AdvanceClock(limits.KeepAliveTimeout);
106+
Http3Api.AdvanceClock(limits.KeepAliveTimeout);
107+
Http3Api.AdvanceClock(limits.KeepAliveTimeout);
108+
109+
requestFinishedTcs.SetResult();
110+
111+
await requestStream.ExpectHeadersAsync();
112+
113+
await requestStream.ExpectReceiveEndOfStream();
114+
await requestStream.OnDisposedTask.DefaultTimeout();
115+
116+
Http3Api.AdvanceClock(limits.KeepAliveTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1));
117+
118+
await Http3Api.WaitForConnectionStopAsync(4, false, expectedErrorCode: Http3ErrorCode.NoError);
119+
}
25120

26121
[Fact]
27122
public async Task HEADERS_IncompleteFrameReceivedWithinRequestHeadersTimeout_StreamError()

0 commit comments

Comments
 (0)