Skip to content

Commit 8fad574

Browse files
author
Nate McMaster
committed
Merge the source code from aspnet/HttpClientFactory into this repo
2 parents 888bcba + c38f9c1 commit 8fad574

File tree

54 files changed

+7222
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+7222
-0
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using BenchmarkDotNet.Columns;
5+
using BenchmarkDotNet.Configs;
6+
using BenchmarkDotNet.Diagnosers;
7+
using BenchmarkDotNet.Engines;
8+
using BenchmarkDotNet.Jobs;
9+
using BenchmarkDotNet.Validators;
10+
11+
namespace Microsoft.Extensions.Http.Performance
12+
{
13+
public class CoreConfig : ManualConfig
14+
{
15+
public CoreConfig()
16+
: this(Job.Core
17+
.WithRemoveOutliers(false)
18+
.With(new GcMode() { Server = true })
19+
.With(RunStrategy.Throughput)
20+
.WithLaunchCount(3)
21+
.WithWarmupCount(5)
22+
.WithTargetCount(10))
23+
{
24+
Add(JitOptimizationsValidator.FailOnError);
25+
}
26+
27+
public CoreConfig(Job job)
28+
{
29+
Add(DefaultConfig.Instance);
30+
31+
Add(MemoryDiagnoser.Default);
32+
Add(StatisticColumn.OperationsPerSecond);
33+
34+
Add(job);
35+
}
36+
}
37+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Net.Http;
6+
using System.Net.Http.Headers;
7+
using System.Threading.Tasks;
8+
using BenchmarkDotNet.Attributes;
9+
using Microsoft.Extensions.DependencyInjection;
10+
11+
namespace Microsoft.Extensions.Http.Performance
12+
{
13+
[ParameterizedJobConfig(typeof(CoreConfig))]
14+
public class CreationOverheadBenchmark
15+
{
16+
private const int Iterations = 100;
17+
18+
public CreationOverheadBenchmark()
19+
{
20+
Handler = new FakeClientHandler();
21+
22+
var serviceCollection = new ServiceCollection();
23+
serviceCollection.AddHttpClient("example", c =>
24+
{
25+
c.BaseAddress = new Uri("http://example.com/");
26+
c.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
27+
})
28+
.ConfigurePrimaryHttpMessageHandler(() => Handler);
29+
30+
var services = serviceCollection.BuildServiceProvider();
31+
Factory = services.GetRequiredService<IHttpClientFactory>();
32+
}
33+
34+
public IHttpClientFactory Factory { get; }
35+
36+
public HttpMessageHandler Handler { get; }
37+
38+
[Benchmark(
39+
Description = "use IHttpClientFactory",
40+
OperationsPerInvoke = Iterations)]
41+
public async Task CreateClient()
42+
{
43+
for (var i = 0; i < Iterations; i++)
44+
{
45+
var client = Factory.CreateClient("example");
46+
47+
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "api/Products"));
48+
response.EnsureSuccessStatusCode();
49+
}
50+
}
51+
52+
[Benchmark(
53+
Description = "new HttpClient",
54+
Baseline = true,
55+
OperationsPerInvoke = Iterations)]
56+
public async Task Baseline()
57+
{
58+
for (var i = 0; i < Iterations; i++)
59+
{
60+
var client = new HttpClient(Handler, disposeHandler: false)
61+
{
62+
BaseAddress = new Uri("http://example.com/"),
63+
DefaultRequestHeaders =
64+
{
65+
Accept =
66+
{
67+
new MediaTypeWithQualityHeaderValue("application/json"),
68+
}
69+
},
70+
};
71+
72+
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "api/Products"));
73+
response.EnsureSuccessStatusCode();
74+
}
75+
}
76+
}
77+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Net;
6+
using System.Net.Http;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
10+
namespace Microsoft.Extensions.Http.Performance
11+
{
12+
internal class FakeClientHandler : HttpMessageHandler
13+
{
14+
public TimeSpan Latency { get; set; } = TimeSpan.FromMilliseconds(10);
15+
16+
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
17+
{
18+
await Task.Yield();
19+
20+
var response = new HttpResponseMessage(HttpStatusCode.OK);
21+
response.RequestMessage = request;
22+
return response;
23+
}
24+
}
25+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using Microsoft.Extensions.Logging;
6+
7+
namespace Microsoft.Extensions.Http.Performance
8+
{
9+
internal class FakeLoggerProvider : ILoggerProvider
10+
{
11+
public bool IsEnabled { get; set; }
12+
13+
public ILogger CreateLogger(string categoryName)
14+
{
15+
return new Logger(this);
16+
}
17+
18+
public void Dispose()
19+
{
20+
}
21+
22+
private class Logger : ILogger
23+
{
24+
private FakeLoggerProvider _provider;
25+
26+
public Logger(FakeLoggerProvider provider)
27+
{
28+
_provider = provider;
29+
}
30+
31+
public IDisposable BeginScope<TState>(TState state)
32+
{
33+
return null;
34+
}
35+
36+
public bool IsEnabled(LogLevel logLevel)
37+
{
38+
return _provider.IsEnabled;
39+
}
40+
41+
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
42+
{
43+
}
44+
}
45+
}
46+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Net.Http;
6+
using System.Net.Http.Headers;
7+
using System.Threading.Tasks;
8+
using BenchmarkDotNet.Attributes;
9+
using Microsoft.Extensions.DependencyInjection;
10+
using Microsoft.Extensions.Logging;
11+
12+
namespace Microsoft.Extensions.Http.Performance
13+
{
14+
[ParameterizedJobConfig(typeof(CoreConfig))]
15+
public class LoggingOverheadBenchmark
16+
{
17+
private const int Iterations = 100;
18+
19+
public LoggingOverheadBenchmark()
20+
{
21+
Handler = new FakeClientHandler();
22+
LoggerProvider = new FakeLoggerProvider();
23+
24+
var serviceCollection = new ServiceCollection();
25+
serviceCollection.AddLogging(b => b.AddProvider(LoggerProvider));
26+
serviceCollection.AddHttpClient("example", c =>
27+
{
28+
c.BaseAddress = new Uri("http://example.com/");
29+
c.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
30+
})
31+
.ConfigurePrimaryHttpMessageHandler(() => Handler);
32+
33+
var services = serviceCollection.BuildServiceProvider();
34+
Factory = services.GetRequiredService<IHttpClientFactory>();
35+
}
36+
37+
private IHttpClientFactory Factory { get; }
38+
39+
private HttpMessageHandler Handler { get; }
40+
41+
private FakeLoggerProvider LoggerProvider { get; }
42+
43+
[Benchmark(
44+
Description = "logging on",
45+
OperationsPerInvoke = Iterations)]
46+
public async Task LoggingOn()
47+
{
48+
LoggerProvider.IsEnabled = true;
49+
50+
for (var i = 0; i < Iterations; i++)
51+
{
52+
var client = Factory.CreateClient("example");
53+
54+
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "api/Products"));
55+
response.EnsureSuccessStatusCode();
56+
}
57+
}
58+
59+
[Benchmark(
60+
Description = "logging off",
61+
OperationsPerInvoke = Iterations)]
62+
public async Task LoggingOff()
63+
{
64+
LoggerProvider.IsEnabled = false;
65+
66+
for (var i = 0; i < Iterations; i++)
67+
{
68+
var client = Factory.CreateClient("example");
69+
70+
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "api/Products"));
71+
response.EnsureSuccessStatusCode();
72+
}
73+
}
74+
}
75+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
5+
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netcoreapp2.0</TargetFrameworks>
6+
<OutputType>Exe</OutputType>
7+
<ServerGarbageCollection>true</ServerGarbageCollection>
8+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
9+
<IsPackable>false</IsPackable>
10+
</PropertyGroup>
11+
12+
<ItemGroup>
13+
<Compile Include="$(SharedSourceRoot)BenchmarkRunner\**\*.cs" />
14+
</ItemGroup>
15+
16+
<ItemGroup>
17+
<Reference Include="Microsoft.Extensions.Http" />
18+
<Reference Include="BenchmarkDotNet" />
19+
<Reference Include="Microsoft.Extensions.DependencyInjection" />
20+
</ItemGroup>
21+
22+
</Project>
23+
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Diagnostics;
6+
using System.Threading;
7+
8+
namespace Microsoft.Extensions.Http
9+
{
10+
// Thread-safety: We treat this class as immutable except for the timer. Creating a new object
11+
// for the 'expiry' pool simplifies the threading requirements significantly.
12+
internal class ActiveHandlerTrackingEntry
13+
{
14+
private readonly object _lock;
15+
private bool _timerInitialized;
16+
private Timer _timer;
17+
private TimerCallback _callback;
18+
19+
public ActiveHandlerTrackingEntry(string name, LifetimeTrackingHttpMessageHandler handler, TimeSpan lifetime)
20+
{
21+
Name = name;
22+
Handler = handler;
23+
Lifetime = lifetime;
24+
25+
_lock = new object();
26+
}
27+
28+
public LifetimeTrackingHttpMessageHandler Handler { get; private set; }
29+
30+
public TimeSpan Lifetime { get; }
31+
32+
public string Name { get; }
33+
34+
public void StartExpiryTimer(TimerCallback callback)
35+
{
36+
if (Lifetime == Timeout.InfiniteTimeSpan)
37+
{
38+
return; // never expires.
39+
}
40+
41+
if (Volatile.Read(ref _timerInitialized))
42+
{
43+
return;
44+
}
45+
46+
StartExpiryTimerSlow(callback);
47+
}
48+
49+
private void StartExpiryTimerSlow(TimerCallback callback)
50+
{
51+
Debug.Assert(Lifetime != Timeout.InfiniteTimeSpan);
52+
53+
lock (_lock)
54+
{
55+
if (Volatile.Read(ref _timerInitialized))
56+
{
57+
return;
58+
}
59+
60+
_callback = callback;
61+
_timer = new Timer(Timer_Tick, null, Lifetime, Timeout.InfiniteTimeSpan);
62+
63+
Volatile.Write(ref _timerInitialized, true);
64+
}
65+
}
66+
67+
private void Timer_Tick(object state)
68+
{
69+
Debug.Assert(_callback != null);
70+
Debug.Assert(_timer != null);
71+
72+
lock (_lock)
73+
{
74+
_timer.Dispose();
75+
_timer = null;
76+
77+
_callback(this);
78+
}
79+
}
80+
}
81+
}

0 commit comments

Comments
 (0)