Skip to content

Commit 229e496

Browse files
authored
3.1: Reset KeepAliveTimeout on HTTP/2 ping (#24858)
* Reset KeepAliveTimeout on HTTP/2 ping (#24644) * Fix flaky keepalive ping test (#24804) * Fix flaky keepalive ping test * Clean up stream * Remove quarantine attribute * Fix test
1 parent 9034fa2 commit 229e496

File tree

2 files changed

+76
-0
lines changed

2 files changed

+76
-0
lines changed

src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,12 @@ private Task ProcessPingFrameAsync(in ReadOnlySequence<byte> payload)
747747
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(_incomingFrame.Type, 8), Http2ErrorCode.FRAME_SIZE_ERROR);
748748
}
749749

750+
// Incoming ping resets connection keep alive timeout
751+
if (TimeoutControl.TimerReason == TimeoutReason.KeepAlive)
752+
{
753+
TimeoutControl.ResetTimeout(Limits.KeepAliveTimeout.Ticks, TimeoutReason.KeepAlive);
754+
}
755+
750756
if (_incomingFrame.PingAck)
751757
{
752758
// TODO: verify that payload is equal to the outgoing PING frame

src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TimeoutTests.cs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,76 @@ await ExpectAsync(Http2FrameType.HEADERS,
120120
_mockTimeoutHandler.VerifyNoOtherCalls();
121121
}
122122

123+
[Fact]
124+
public async Task PING_WithinKeepAliveTimeout_ResetKeepAliveTimeout()
125+
{
126+
var mockSystemClock = _serviceContext.MockSystemClock;
127+
var limits = _serviceContext.ServerOptions.Limits;
128+
129+
_timeoutControl.Initialize(mockSystemClock.UtcNow.Ticks);
130+
131+
CreateConnection();
132+
133+
await InitializeConnectionAsync(_noopApplication);
134+
135+
// Connection starts and sets keep alive timeout
136+
_mockTimeoutControl.Verify(c => c.SetTimeout(It.IsAny<long>(), TimeoutReason.KeepAlive), Times.Once);
137+
_mockTimeoutControl.Verify(c => c.ResetTimeout(It.IsAny<long>(), TimeoutReason.KeepAlive), Times.Never);
138+
139+
await SendPingAsync(Http2PingFrameFlags.NONE);
140+
await ExpectAsync(Http2FrameType.PING,
141+
withLength: 8,
142+
withFlags: (byte)Http2PingFrameFlags.ACK,
143+
withStreamId: 0);
144+
145+
// Server resets keep alive timeout
146+
_mockTimeoutControl.Verify(c => c.ResetTimeout(It.IsAny<long>(), TimeoutReason.KeepAlive), Times.Once);
147+
}
148+
149+
[Fact]
150+
public async Task PING_NoKeepAliveTimeout_DoesNotResetKeepAliveTimeout()
151+
{
152+
var mockSystemClock = _serviceContext.MockSystemClock;
153+
var limits = _serviceContext.ServerOptions.Limits;
154+
155+
_timeoutControl.Initialize(mockSystemClock.UtcNow.Ticks);
156+
157+
CreateConnection();
158+
159+
await InitializeConnectionAsync(_echoApplication);
160+
161+
// Connection starts and sets keep alive timeout
162+
_mockTimeoutControl.Verify(c => c.SetTimeout(It.IsAny<long>(), TimeoutReason.KeepAlive), Times.Once);
163+
_mockTimeoutControl.Verify(c => c.ResetTimeout(It.IsAny<long>(), TimeoutReason.KeepAlive), Times.Never);
164+
_mockTimeoutControl.Verify(c => c.CancelTimeout(), Times.Never);
165+
166+
// Stream will stay open because it is waiting for request body to end
167+
await StartStreamAsync(1, _browserRequestHeaders, endStream: false);
168+
169+
// Starting a stream cancels the keep alive timeout
170+
_mockTimeoutControl.Verify(c => c.CancelTimeout(), Times.Once);
171+
172+
await SendPingAsync(Http2PingFrameFlags.NONE);
173+
await ExpectAsync(Http2FrameType.PING,
174+
withLength: 8,
175+
withFlags: (byte)Http2PingFrameFlags.ACK,
176+
withStreamId: 0);
177+
178+
// Server doesn't reset keep alive timeout because it isn't running
179+
_mockTimeoutControl.Verify(c => c.ResetTimeout(It.IsAny<long>(), TimeoutReason.KeepAlive), Times.Never);
180+
181+
// End stream
182+
await SendDataAsync(1, _helloWorldBytes, endStream: true);
183+
await ExpectAsync(Http2FrameType.HEADERS,
184+
withLength: 37,
185+
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
186+
withStreamId: 1);
187+
await ExpectAsync(Http2FrameType.DATA,
188+
withLength: _helloWorldBytes.Length,
189+
withFlags: (byte)Http2DataFrameFlags.NONE,
190+
withStreamId: 1);
191+
}
192+
123193
[Fact]
124194
public async Task HEADERS_ReceivedWithoutAllCONTINUATIONs_WithinRequestHeadersTimeout_AbortsConnection()
125195
{

0 commit comments

Comments
 (0)