Skip to content

Commit a58dc46

Browse files
committed
New dictionary
1 parent 0e7641d commit a58dc46

File tree

2 files changed

+14
-20
lines changed

2 files changed

+14
-20
lines changed

src/HttpClientCache.cs

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
11
using Soenneker.Dtos.HttpClientOptions;
22
using Soenneker.Extensions.Enumerable;
33
using Soenneker.Extensions.ValueTask;
4+
using Soenneker.Dictionaries.SingletonKeys;
45
using Soenneker.Utils.HttpClientCache.Abstract;
56
using Soenneker.Utils.Runtime;
6-
using Soenneker.Utils.SingletonDictionary;
77
using System;
8-
using System.Collections.Concurrent;
98
using System.Collections.Generic;
109
using System.Net;
1110
using System.Net.Http;
1211
using System.Net.Security;
1312
using System.Runtime.CompilerServices;
1413
using System.Threading;
1514
using System.Threading.Tasks;
15+
using Soenneker.Dictionaries.Singletons;
1616

1717
namespace Soenneker.Utils.HttpClientCache;
1818

1919
///<inheritdoc cref="IHttpClientCache"/>
2020
public sealed class HttpClientCache : IHttpClientCache
2121
{
2222
private readonly SingletonDictionary<HttpClient, OptionsFactory> _httpClients;
23-
private readonly ConcurrentDictionary<HandlerKey, SocketsHttpHandler> _handlers = new();
23+
private readonly SingletonKeyDictionary<HandlerKey, SocketsHttpHandler> _handlers;
2424

2525
private static readonly bool _isBrowser = RuntimeUtil.IsBrowser();
2626

@@ -32,9 +32,12 @@ public HttpClientCache()
3232
{
3333
// Use method group to avoid a closure and still access instance state.
3434
_httpClients = new SingletonDictionary<HttpClient, OptionsFactory>(InitializeHttpClient);
35+
36+
// Handlers are expensive and should be reused; use a keyed singleton dictionary to guarantee a single handler per key.
37+
_handlers = new SingletonKeyDictionary<HandlerKey, SocketsHttpHandler>(static k => CreateHandlerFromKey(in k));
3538
}
3639

37-
private async ValueTask<HttpClient> InitializeHttpClient(string _, CancellationToken cancellationToken, OptionsFactory factory)
40+
private async ValueTask<HttpClient> InitializeHttpClient(string _, OptionsFactory factory, CancellationToken cancellationToken)
3841
{
3942
HttpClientOptions? options = await factory.Invoke(cancellationToken)
4043
.NoSync();
@@ -144,8 +147,7 @@ private SocketsHttpHandler GetOrCreateHandler(HttpClientOptions? options)
144147
KeepAlivePingPolicy: options?.KeepAlivePingPolicy, UseProxy: options?.UseProxy, Proxy: proxy, MaxResponseDrainSize: options?.MaxResponseDrainSize,
145148
MaxResponseHeadersLength: options?.MaxResponseHeadersLength, SslOptions: sslOptions);
146149

147-
// static factory => no closure allocation
148-
return _handlers.GetOrAdd(key, static k => CreateHandlerFromKey(k));
150+
return _handlers.GetSync(key);
149151
}
150152

151153
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -212,24 +214,15 @@ public ValueTask Remove(string id, CancellationToken cancellationToken = default
212214
public void RemoveSync(string id, CancellationToken cancellationToken = default) =>
213215
_httpClients.RemoveSync(id, cancellationToken);
214216

215-
private void DisposeHandlers()
216-
{
217-
foreach (KeyValuePair<HandlerKey, SocketsHttpHandler> kvp in _handlers)
218-
{
219-
if (_handlers.TryRemove(kvp.Key, out SocketsHttpHandler? handler))
220-
handler.Dispose();
221-
}
222-
}
223-
224-
public ValueTask DisposeAsync()
217+
public async ValueTask DisposeAsync()
225218
{
226-
DisposeHandlers();
227-
return _httpClients.DisposeAsync();
219+
await _handlers.DisposeAsync();
220+
await _httpClients.DisposeAsync();
228221
}
229222

230223
public void Dispose()
231224
{
232-
DisposeHandlers();
225+
_handlers.Dispose();
233226
_httpClients.Dispose();
234227
}
235228
}

src/Soenneker.Utils.HttpClientCache.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,9 @@
4343

4444
<ItemGroup>
4545
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.1" />
46+
<PackageReference Include="Soenneker.Dictionaries.Singletons" Version="4.0.1" />
4647
<PackageReference Include="Soenneker.Dtos.HttpClientOptions" Version="4.0.13" />
4748
<PackageReference Include="Soenneker.Utils.Runtime" Version="4.0.910" />
48-
<PackageReference Include="Soenneker.Utils.SingletonDictionary" Version="4.0.1150" />
49+
4950
</ItemGroup>
5051
</Project>

0 commit comments

Comments
 (0)