Skip to content

Commit

Permalink
Fixes and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
JamesNK committed Jun 6, 2020
1 parent 3ff94fe commit 631f04d
Show file tree
Hide file tree
Showing 7 changed files with 320 additions and 129 deletions.
3 changes: 3 additions & 0 deletions src/Servers/Kestrel/Core/src/CoreStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -608,4 +608,7 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l
<data name="Http2ErrorKeepAliveTimeout" xml:space="preserve">
<value>Timeout while waiting for a keep alive ping acknowledgement.</value>
</data>
<data name="ArgumentTimeSpanGreaterThan" xml:space="preserve">
<value>A TimeSpan value greater than {value} is required.</value>
</data>
</root>
28 changes: 24 additions & 4 deletions src/Servers/Kestrel/Core/src/Http2Limits.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
/// </summary>
public class Http2Limits
{
private static readonly TimeSpan MinimumKeepAliveInterval = TimeSpan.FromSeconds(1);

private int _maxStreamsPerConnection = 100;
private int _headerTableSize = (int)Http2PeerSettings.DefaultHeaderTableSize;
private int _maxFrameSize = (int)Http2PeerSettings.DefaultMaxFrameSize;
Expand Down Expand Up @@ -145,28 +147,46 @@ public int InitialStreamWindowSize
}
}

/// <summary>
/// Gets or sets the keep alive ping interval. The server will send a keep alive ping to the client if it
/// doesn't see any activity after this interval elapses. This property is used together with
/// <see cref="KeepAlivePingTimeout"/> to close inactive connections.
/// <para>
/// Value must be greater than 1 second. Set to <c>null</c> or <see cref="Timeout.InfiniteTimeSpan"/> to disable
/// the keep alive ping interval. Defaults to <c>null</c>.
/// </para>
/// </summary>
public TimeSpan? KeepAlivePingInterval
{
get => _keepAlivePingInterval;
set
{
if (value <= TimeSpan.Zero && value != Timeout.InfiniteTimeSpan)
if (value < MinimumKeepAliveInterval && value != Timeout.InfiniteTimeSpan)
{
throw new ArgumentOutOfRangeException(nameof(value), CoreStrings.PositiveTimeSpanRequired);
throw new ArgumentOutOfRangeException(nameof(value), CoreStrings.FormatArgumentTimeSpanGreaterThan(MinimumKeepAliveInterval));
}

_keepAlivePingInterval = value;
}
}

/// <summary>
/// Gets or sets the keep alive ping timeout. Keep alive pings are sent when a period of inactivity exceeds
/// the configured <see cref="KeepAlivePingInterval"/> value. The server will close the connection if it
/// doesn't receive an acknowledgement of the keep alive ping within the timeout.
/// <para>
/// Value must be greater than 1 second. Set to <see cref="Timeout.InfiniteTimeSpan"/> to disable the keep
/// alive ping timeout. Defaults to 20 seconds.
/// </para>
/// </summary>
public TimeSpan KeepAlivePingTimeout
{
get => _keepAlivePingTimeout;
set
{
if (value <= TimeSpan.Zero && value != Timeout.InfiniteTimeSpan)
if (value < MinimumKeepAliveInterval && value != Timeout.InfiniteTimeSpan)
{
throw new ArgumentOutOfRangeException(nameof(value), CoreStrings.PositiveTimeSpanRequired);
throw new ArgumentOutOfRangeException(nameof(value), CoreStrings.FormatArgumentTimeSpanGreaterThan(MinimumKeepAliveInterval));
}

_keepAlivePingTimeout = value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ internal partial class Http2Connection : IHttp2StreamLifetimeHandler, IHttpHeade
private readonly HPackDecoder _hpackDecoder;
private readonly InputFlowControl _inputFlowControl;
private readonly OutputFlowControl _outputFlowControl = new OutputFlowControl(new MultipleAwaitableProvider(), Http2PeerSettings.DefaultInitialWindowSize);
private readonly Http2ConnectionKeepAlive _keepAlive;

private readonly Http2PeerSettings _serverSettings = new Http2PeerSettings();
private readonly Http2PeerSettings _clientSettings = new Http2PeerSettings();
Expand All @@ -67,6 +66,7 @@ internal partial class Http2Connection : IHttp2StreamLifetimeHandler, IHttpHeade
private int _isClosed;

// Internal for testing
internal readonly Http2KeepAlive _keepAlive;
internal readonly Dictionary<int, Http2Stream> _streams = new Dictionary<int, Http2Stream>();
internal Http2StreamStack StreamPool;

Expand Down Expand Up @@ -109,7 +109,7 @@ public Http2Connection(HttpConnectionContext context)

if (http2Limits.KeepAlivePingInterval != null && http2Limits.KeepAlivePingInterval != Timeout.InfiniteTimeSpan)
{
_keepAlive = new Http2ConnectionKeepAlive(
_keepAlive = new Http2KeepAlive(
http2Limits.KeepAlivePingInterval.GetValueOrDefault(),
http2Limits.KeepAlivePingTimeout,
context.ServiceContext.SystemClock);
Expand Down Expand Up @@ -223,7 +223,8 @@ public async Task ProcessRequestsAsync<TContext>(IHttpApplication<TContext> appl
var state = _keepAlive.ProcessKeepAlive(!result.IsCanceled);
if (state == KeepAliveState.SendPing)
{
await _frameWriter.WritePingAsync(Http2PingFrameFlags.NONE, Http2ConnectionKeepAlive.PingPayload);
await _frameWriter.WritePingAsync(Http2PingFrameFlags.NONE, Http2KeepAlive.PingPayload);
_keepAlive.PingSent();
}
else if (state == KeepAliveState.Timeout)
{
Expand Down

This file was deleted.

116 changes: 116 additions & 0 deletions src/Servers/Kestrel/Core/src/Internal/Http2/Http2KeepAlive.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Buffers;
using System.Diagnostics;
using System.Threading;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;

namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
internal enum KeepAliveState
{
None,
SendPing,
PingSent,
Timeout
}

internal class Http2KeepAlive
{
internal static readonly ReadOnlySequence<byte> PingPayload = new ReadOnlySequence<byte>(new byte[8]);

private readonly TimeSpan _keepAliveInterval;
private readonly TimeSpan _keepAliveTimeout;
private readonly ISystemClock _systemClock;
private readonly object _lock = new object();
private KeepAliveState _state;
private bool _bytesReceivedCurrentTick;
private long _lastBytesReceivedTimestamp;
private long _pingSentTimestamp;

public Http2KeepAlive(TimeSpan keepAliveInterval, TimeSpan keepAliveTimeout, ISystemClock systemClock)
{
_keepAliveInterval = keepAliveInterval;
_keepAliveTimeout = keepAliveTimeout;
_systemClock = systemClock;
}

internal KeepAliveState ProcessKeepAlive(bool dataReceived)
{
lock (_lock)
{
if (dataReceived)
{
_bytesReceivedCurrentTick = true;
}
return _state;
}
}

public void PingSent()
{
lock (_lock)
{
_state = KeepAliveState.PingSent;
_pingSentTimestamp = _systemClock.UtcNowTicks;
}
}

public void PingAckReceived()
{
lock (_lock)
{
if (_state == KeepAliveState.PingSent)
{
_pingSentTimestamp = 0;
_state = KeepAliveState.None;
}
}
}

public void Tick(DateTimeOffset now)
{
var timestamp = now.Ticks;

lock (_lock)
{
// Bytes were received since the last tick.
// Update a timestamp of when bytes were last received.
if (_bytesReceivedCurrentTick)
{
_lastBytesReceivedTimestamp = timestamp;
_bytesReceivedCurrentTick = false;
}

switch (_state)
{
case KeepAliveState.None:
// Check whether keep alive interval has passed since last bytes received
if (timestamp > (_lastBytesReceivedTimestamp + _keepAliveInterval.Ticks))
{
_state = KeepAliveState.SendPing;
}
return;
case KeepAliveState.SendPing:
return;
case KeepAliveState.PingSent:
if (_keepAliveTimeout != Timeout.InfiniteTimeSpan)
{
if (timestamp > (_pingSentTimestamp + _keepAliveTimeout.Ticks))
{
_pingSentTimestamp = 0;
_state = KeepAliveState.Timeout;
}
}
return;
case KeepAliveState.Timeout:
return;
}
}

Debug.Fail("Should never reach here.");
}
}
}
Loading

0 comments on commit 631f04d

Please sign in to comment.