Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
17ddbce
init
Jul 21, 2020
2afc76f
Http2 ping
Aug 3, 2020
c590b7c
Merge remote-tracking branch 'upstream/master' into jajahoda/httpping
Aug 3, 2020
eb37332
clean up
Aug 3, 2020
712479f
enable tests
Aug 3, 2020
95bc2f3
Fix mac build
Aug 3, 2020
de89629
Merge branch 'master' of github.com:dotnet/runtime into jajahoda/http…
Aug 3, 2020
b3e4b3d
Split HttpHandlerDefaults
Aug 4, 2020
e06f092
Merge branch 'master' of github.com:dotnet/runtime into jajahoda/http…
Aug 4, 2020
6e2f371
Merge remote-tracking branch 'origin/jajahoda/httpping' into jajahoda…
Aug 4, 2020
b7ed6ce
Fix mac build
Aug 4, 2020
20f44cf
Fix browser and test csproj
Aug 4, 2020
b684730
Apply PR comments
Aug 5, 2020
014c8d6
Merge remote-tracking branch 'upstream/master' into jajahoda/httpping
Aug 5, 2020
60a757d
Remove Http2KeepAlice class
Aug 5, 2020
886a2da
fix trace message
Aug 5, 2020
863f636
ProcessPingAck no longer return bool
Aug 5, 2020
a987919
Apply suggestions from code review
aik-jahoda Aug 6, 2020
b221f43
Add argument out of ramnge message
Aug 6, 2020
be468cd
Merge branch 'jajahoda/httpping' of github.com:aik-jahoda/runtime int…
Aug 6, 2020
db89b58
Apply PR comments
Aug 6, 2020
02785d6
SocketsHttpHandler setters
Aug 6, 2020
d354a29
Merge remote-tracking branch 'upstream/master' into jajahoda/httpping
Aug 7, 2020
b83cfc3
Update src/libraries/Common/tests/System/Net/Http/Http2LoopbackConnec…
aik-jahoda Aug 7, 2020
63e3d3f
Update src/libraries/System.Net.Http/src/Resources/Strings.resx
aik-jahoda Aug 7, 2020
258f1c4
Apply suggestions from code review
aik-jahoda Aug 7, 2020
2695f2e
PR comments
Aug 7, 2020
0ea45e8
Merge branch 'master' into jajahoda/httpping
ManickaP Aug 11, 2020
7078d8e
Addressed the last batch of PR feedback.
ManickaP Aug 11, 2020
9c1b6e7
Merge branch 'master' into jajahoda/httpping
ManickaP Aug 12, 2020
86c8413
Fixed KeepAlive test dependency on frame chronology.
ManickaP Aug 12, 2020
c10beb9
Fixed FW compilation.
ManickaP Aug 12, 2020
b9c6b33
Split and moved tests to theirs proper places.
ManickaP Aug 12, 2020
40e0e5c
More test fixing for super slow CI.
ManickaP Aug 12, 2020
3a753b7
Exception texts.
ManickaP Aug 13, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ internal static class HttpHandlerDefaults
public static readonly TimeSpan DefaultPooledConnectionLifetime = Timeout.InfiniteTimeSpan;
public static readonly TimeSpan DefaultPooledConnectionIdleTimeout = TimeSpan.FromMinutes(2);
public static readonly TimeSpan DefaultExpect100ContinueTimeout = TimeSpan.FromSeconds(1);
public static readonly TimeSpan DefaultKeepAlivePingTimeout = TimeSpan.FromSeconds(20);
public static readonly TimeSpan DefaultKeepAlivePingDelay = TimeSpan.FromSeconds(0);
public const HttpKeepAlivePingPolicy DefaultKeepAlivePingPolicy = HttpKeepAlivePingPolicy.Always;
public static readonly TimeSpan DefaultConnectTimeout = Timeout.InfiniteTimeSpan;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Net.Http
{
public enum HttpKeepAlivePingPolicy
{
WithActiveRequests,
Always
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,25 @@ public async Task PingPong()
Assert.Equal(pingData, pingAck.Data);
}

public async Task<PingFrame> ReadPingAsync(TimeSpan timeout)
{
Frame frame = await ReadFrameAsync(timeout).ConfigureAwait(false);
Assert.NotNull(frame);
Assert.Equal(FrameType.Ping, frame.Type);
Assert.Equal(0, frame.StreamId);
Assert.False(frame.AckFlag);

PingFrame ping = frame as PingFrame;
Assert.NotNull(ping);
return ping;
}

public async Task SendPingAckAsync(byte[] payload)
{
PingFrame pingAck = new PingFrame(payload, FrameFlags.Ack, 0);
await WriteFrameAsync(pingAck).ConfigureAwait(false);
}

public async Task SendDefaultResponseHeadersAsync(int streamId)
{
byte[] headers = new byte[] { 0x88 }; // Encoding for ":status: 200"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,10 @@ public WinHttpHandler() { }
protected override void Dispose(bool disposing) { }
protected override System.Threading.Tasks.Task<System.Net.Http.HttpResponseMessage> SendAsync(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { throw null; }
}

public enum HttpKeepAlivePingPolicy
{
WithActiveRequests,
Always
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@
<Compile Include="$(CommonPath)\Interop\Windows\Crypt32\Interop.certificates_types.cs"
Link="Common\Interop\Windows\Crypt32\Interop.certificates_types.cs" />
<Compile Include="$(CommonPath)\Interop\Windows\Crypt32\Interop.certificates.cs"
Link="Common\Interop\Windows\Crypt32\Interop.certificates.cs" />
Link="Common\Interop\Windows\Crypt32\Interop.certificates.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.FormatMessage.cs"
Link="Common\Interop\Windows\Kernel32\Interop.FormatMessage.cs" />
Link="Common\Interop\Windows\Kernel32\Interop.FormatMessage.cs" />
<Compile Include="$(CommonPath)\Interop\Windows\Kernel32\Interop.GetModuleHandle.cs"
Link="Common\Interop\Windows\Kernel32\Interop.GetModuleHandle.cs" />
<Compile Include="$(CommonPath)\Interop\Windows\Interop.HRESULT_FROM_WIN32.cs"
Expand All @@ -42,7 +42,7 @@
<Compile Include="$(CommonPath)\System\Net\HttpKnownHeaderNames.cs"
Link="Common\System\Net\HttpKnownHeaderNames.cs" />
<Compile Include="$(CommonPath)\System\Net\HttpKnownHeaderNames.TryGetHeaderName.cs"
Link="Common\System\Net\HttpKnownHeaderNames.TryGetHeaderName.cs" />
Link="Common\System\Net\HttpKnownHeaderNames.TryGetHeaderName.cs" />
<Compile Include="$(CommonPath)System\Net\HttpStatusDescription.cs"
Link="Common\System\Net\Http\HttpStatusDescription.cs" />
<Compile Include="$(CommonPath)\System\Net\SecurityProtocol.cs"
Expand All @@ -51,6 +51,8 @@
Link="Common\System\Net\UriScheme.cs" />
<Compile Include="$(CommonPath)\System\Net\Http\HttpHandlerDefaults.cs"
Link="Common\System\Net\Http\HttpHandlerDefaults.cs" />
<Compile Include="$(CommonPath)\System\Net\Http\HttpKeepAlivePingPolicy.cs"
Link="Common\System\Net\Http\HttpKeepAlivePingPolicy.cs" />
<Compile Include="$(CommonPath)\System\Net\Http\WinInetProxyHelper.cs"
Link="Common\System\Net\Http\WinInetProxyHelper.cs" />
<Compile Include="$(CommonPath)\System\Net\Security\CertificateHelper.cs"
Expand All @@ -60,7 +62,7 @@
<Compile Include="$(CommonPath)\System\Runtime\ExceptionServices\ExceptionStackTrace.cs"
Link="Common\System\Runtime\ExceptionServices\ExceptionStackTrace.cs" />
<Compile Include="$(CommonPath)\System\Threading\Tasks\RendezvousAwaitable.cs"
Link="Common\System\Threading\Tasks\RendezvousAwaitable.cs" />
Link="Common\System\Threading\Tasks\RendezvousAwaitable.cs" />
<Compile Include="$(CommonPath)System\Threading\Tasks\TaskToApm.cs"
Link="Common\System\Threading\Tasks\TaskToApm.cs" />
<Compile Include="System\Net\Http\NetEventSource.WinHttpHandler.cs" />
Expand Down
8 changes: 8 additions & 0 deletions src/libraries/System.Net.Http/ref/System.Net.Http.cs
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,9 @@ public SocketsHttpHandler() { }
public System.Net.ICredentials? Credentials { get { throw null; } set { } }
public System.Net.ICredentials? DefaultProxyCredentials { get { throw null; } set { } }
public System.TimeSpan Expect100ContinueTimeout { get { throw null; } set { } }
public System.TimeSpan KeepAlivePingDelay { get { throw null; } set { } }
public System.TimeSpan KeepAlivePingTimeout { get { throw null; } set { } }
public HttpKeepAlivePingPolicy KeepAlivePingPolicy { get { throw null; } set { } }
public int MaxAutomaticRedirections { get { throw null; } set { } }
public int MaxConnectionsPerServer { get { throw null; } set { } }
public int MaxResponseDrainSize { get { throw null; } set { } }
Expand All @@ -359,6 +362,11 @@ protected override void Dispose(bool disposing) { }
protected internal override System.Threading.Tasks.Task<System.Net.Http.HttpResponseMessage> SendAsync(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { throw null; }
public bool EnableMultipleHttp2Connections { get { throw null; } set { } }
}
public enum HttpKeepAlivePingPolicy
{
WithActiveRequests,
Always
}
public partial class StreamContent : System.Net.Http.HttpContent
{
public StreamContent(System.IO.Stream content) { }
Expand Down
3 changes: 3 additions & 0 deletions src/libraries/System.Net.Http/src/System.Net.Http.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@
<Compile Include="System\Net\Http\SocketsHttpHandler\FailedProxyCache.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\Http2Connection.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\Http2ConnectionException.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\Http2KeepAlive.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\Http2ProtocolErrorCode.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\Http2ProtocolException.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\Http2Stream.cs" />
Expand Down Expand Up @@ -470,6 +471,8 @@
Link="Common\System\Net\UriScheme.cs" />
<Compile Include="$(CommonPath)\System\Net\Http\HttpHandlerDefaults.cs"
Link="Common\System\Net\Http\HttpHandlerDefaults.cs" />
<Compile Include="$(CommonPath)\System\Net\Http\HttpKeepAlivePingPolicy.cs"
Link="Common\System\Net\Http\HttpKeepAlivePingPolicy.cs" />
<Compile Include="$(CommonPath)\System\Net\Http\WinInetProxyHelper.cs"
Link="Common\System\Net\Http\WinInetProxyHelper.cs" />
<Compile Include="$(CommonPath)\System\Net\Security\CertificateHelper.cs"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Diagnostics;
Expand Down Expand Up @@ -96,6 +97,8 @@ internal sealed partial class Http2Connection : HttpConnectionBase, IDisposable
// Channel options for creating _writeChannel
private static readonly UnboundedChannelOptions s_channelOptions = new UnboundedChannelOptions() { SingleReader = true };

private Http2KeepAlive _keepAlive;

public Http2Connection(HttpConnectionPool pool, Connection connection)
{
_pool = pool;
Expand All @@ -119,6 +122,8 @@ public Http2Connection(HttpConnectionPool pool, Connection connection)
_pendingWindowUpdate = 0;
_idleSinceTickCount = Environment.TickCount64;

_keepAlive = new Http2KeepAlive(this, _pool.Settings);

if (NetEventSource.Log.IsEnabled()) TraceConnection(_stream);
}

Expand Down Expand Up @@ -279,6 +284,8 @@ private async Task ProcessIncomingFramesAsync()
frameHeader = await ReadFrameAsync().ConfigureAwait(false);
if (NetEventSource.Log.IsEnabled()) Trace($"Frame {frameNum}: {frameHeader}.");

_keepAlive.ProcessFrame();

// Process the frame.
switch (frameHeader.Type)
{
Expand Down Expand Up @@ -648,12 +655,6 @@ private void ProcessPingFrame(FrameHeader frameHeader)
ThrowProtocolError();
}

if (frameHeader.AckFlag)
{
// We never send PING, so an ACK indicates a protocol error
ThrowProtocolError();
}

if (frameHeader.PayloadLength != FrameHeader.PingLength)
{
ThrowProtocolError(Http2ProtocolErrorCode.FrameSizeError);
Expand All @@ -666,8 +667,15 @@ private void ProcessPingFrame(FrameHeader frameHeader)
ReadOnlySpan<byte> pingContent = _incomingBuffer.ActiveSpan.Slice(0, FrameHeader.PingLength);
long pingContentLong = BinaryPrimitives.ReadInt64BigEndian(pingContent);

LogExceptions(SendPingAckAsync(pingContentLong));

if (frameHeader.AckFlag)
{
if (!_keepAlive.ProcessPingAck(pingContentLong))
ThrowProtocolError();
}
else
{
LogExceptions(SendPingAckAsync(pingContentLong));
}
_incomingBuffer.Discard(frameHeader.PayloadLength);
}

Expand Down Expand Up @@ -931,6 +939,21 @@ private Task SendPingAckAsync(long pingContent) =>
return true;
});

/// <param name="pingContent">The 8-byte ping content to send, read as a big-endian integer.</param>
private Task SendPingAsync(long pingContent) =>
PerformWriteAsync(FrameHeader.Size + FrameHeader.PingLength, (thisRef: this, pingContent), static (state, writeBuffer) =>
{
if (NetEventSource.Log.IsEnabled()) state.thisRef.Trace("Started writing.");

Debug.Assert(sizeof(long) == FrameHeader.PingLength);

Span<byte> span = writeBuffer.Span;
FrameHeader.WriteTo(span, FrameHeader.PingLength, FrameType.Ping, FrameFlags.None, streamId: 0);
BinaryPrimitives.WriteInt64BigEndian(span.Slice(FrameHeader.Size), state.pingContent);

return true;
});

private Task SendRstStreamAsync(int streamId, Http2ProtocolErrorCode errorCode) =>
PerformWriteAsync(FrameHeader.Size + FrameHeader.RstStreamLength, (thisRef: this, streamId, errorCode), static (s, writeBuffer) =>
{
Expand All @@ -943,6 +966,24 @@ private Task SendRstStreamAsync(int streamId, Http2ProtocolErrorCode errorCode)
return true;
});


internal void HeartBeat()
{
if (_disposed)
return;

try
{
_keepAlive.VerifyKeepAlive();
}
catch (Exception e)
{
if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(ProcessIncomingFramesAsync)}: {e.Message}");

Abort(e);
}
}

private static (ReadOnlyMemory<byte> first, ReadOnlyMemory<byte> rest) SplitBuffer(ReadOnlyMemory<byte> buffer, int maxSize) =>
buffer.Length > maxSize ?
(buffer.Slice(0, maxSize), buffer.Slice(maxSize)) :
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Net.Http.Headers;
using System.Net.Http.HPack;
using System.Net.Security;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;

namespace System.Net.Http
{
internal sealed partial class Http2Connection
{
internal enum KeepAliveState
{
None,
PingSent
}

internal class Http2KeepAlive
{

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: extra empty line

private long _pingPayload;

private readonly TimeSpan _keepAlivePingDelay;
private readonly TimeSpan _keepAlivePingTimeout;
private DateTimeOffset _nextPingRequestTimestamp;
private DateTimeOffset _pingTimeoutTimestamp;
private HttpKeepAlivePingPolicy _keepAlivePingPolicy;
private KeepAliveState _state;
private Http2Connection _connection;

public Http2KeepAlive(Http2Connection connection, HttpConnectionSettings settings)
{
_keepAlivePingDelay = settings._keepAlivePingDelay;
_keepAlivePingTimeout = settings._keepAlivePingTimeout;
_nextPingRequestTimestamp = DateTimeOffset.Now.Add(settings._keepAlivePingDelay);
_keepAlivePingPolicy = settings._keepAlivePingPolicy;
_connection = connection;
}

public void ProcessFrame()
{
_nextPingRequestTimestamp = DateTimeOffset.Now.Add(_keepAlivePingDelay);
}

public bool ProcessPingAck(long payload)
{
if (_state != KeepAliveState.PingSent)
return false;
if (Interlocked.Read(ref _pingPayload) != payload)
return false;
_state = KeepAliveState.None;
ProcessFrame();
return true;
}

public void VerifyKeepAlive()
{
if (_keepAlivePingPolicy == HttpKeepAlivePingPolicy.WithActiveRequests && _connection._httpStreams.Count == 0)
return;

var now = DateTimeOffset.Now;
switch (_state)
{
case KeepAliveState.None:
// Check whether keep alive delay has passed since last frame received
if (_keepAlivePingDelay > TimeSpan.Zero && now > _nextPingRequestTimestamp)
{
// Set the status directly to ping sent and set the timestamp
_state = KeepAliveState.PingSent;
_pingTimeoutTimestamp = now.Add(_keepAlivePingTimeout);
Interlocked.Increment(ref _pingPayload);
_connection.SendPingAsync(_pingPayload);
return;
}
break;
case KeepAliveState.PingSent:
if (_keepAlivePingTimeout != TimeSpan.MaxValue)
{
if (now > _pingTimeoutTimestamp)
Http2Connection.ThrowProtocolError();
}

break;
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1689,6 +1689,17 @@ private static bool GetIsWindows7Or2008R2()
return false;
}

internal void HeartBeat()
{
Http2Connection[]? localHttp2Connections = _http2Connections;
if (localHttp2Connections != null)
{
foreach (var http2Connection in localHttp2Connections)
http2Connection?.HeartBeat();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alnikola will this collection ever have a null connection in it?

Suggested change
http2Connection?.HeartBeat();
http2Connection.HeartBeat();

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, AFAIK this will never be null, the array gets resized with each addition/removal.

}
}


// For diagnostic purposes
public override string ToString() =>
$"{nameof(HttpConnectionPool)} " +
Expand Down
Loading