Skip to content

Commit aa39a1b

Browse files
authored
Cache pre-request BoundConfigurations in DistributedTransport (#166)
Reduces frequent allocations of `BoundConfiguration` for requests with local `IRequestConfiguration`.
1 parent ec85362 commit aa39a1b

File tree

2 files changed

+55
-10
lines changed

2 files changed

+55
-10
lines changed

src/Elastic.Transport/Configuration/IRequestConfiguration.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public interface IRequestConfiguration
6060
bool? DisableSniff { get; }
6161

6262
/// <summary>
63-
/// Whether or not this request should be pipelined. http://en.wikipedia.org/wiki/HTTP_pipelining defaults to true
63+
/// Whether this request should be pipelined. <see href="http://en.wikipedia.org/wiki/HTTP_pipelining"/> defaults to <see langword="true"/>.
6464
/// </summary>
6565
bool? HttpPipeliningEnabled { get; }
6666

src/Elastic.Transport/DistributedTransport.cs

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Collections.Generic;
77
using System.Diagnostics;
88
using System.Linq;
9+
using System.Runtime.CompilerServices;
910
using System.Threading;
1011
using System.Threading.Tasks;
1112
using Elastic.Transport.Diagnostics;
@@ -35,6 +36,8 @@ public class DistributedTransport<TConfiguration> : ITransport<TConfiguration>
3536
{
3637
private readonly ProductRegistration _productRegistration;
3738

39+
private ConditionalWeakTable<RequestConfiguration, BoundConfiguration>? _boundConfigurations;
40+
3841
/// <summary>
3942
/// Transport coordinates the client requests over the node pool nodes and is in charge of falling over on
4043
/// different nodes
@@ -97,15 +100,7 @@ private async ValueTask<TResponse> RequestCoreAsync<TResponse>(
97100

98101
try
99102
{
100-
// Unless per request configuration is provided, we can reuse a BoundConfiguration
101-
// that is specific to this transport. If the IRequestConfiguration is an instance
102-
// of BoundConfiguration we use that cached instance directly without rebinding.
103-
var boundConfiguration = localConfiguration switch
104-
{
105-
BoundConfiguration bc => bc,
106-
{ } rc => new BoundConfiguration(Configuration, rc),
107-
_ => TransportBoundConfiguration
108-
};
103+
var boundConfiguration = BindConfiguration(localConfiguration);
109104

110105
Configuration.OnConfigurationBound?.Invoke(boundConfiguration);
111106

@@ -273,6 +268,56 @@ private async ValueTask<TResponse> RequestCoreAsync<TResponse>(
273268
}
274269
}
275270

271+
private BoundConfiguration BindConfiguration(IRequestConfiguration? localConfiguration)
272+
{
273+
// Unless per request configuration is provided, we can reuse a BoundConfiguration
274+
// that is specific to this transport. If the IRequestConfiguration is an instance
275+
// of BoundConfiguration we use that cached instance directly without rebinding.
276+
return localConfiguration switch
277+
{
278+
BoundConfiguration bc => bc,
279+
RequestConfiguration rc => GetOrCreateBoundConfiguration(rc),
280+
not null => new BoundConfiguration(Configuration, localConfiguration),
281+
_ => TransportBoundConfiguration
282+
};
283+
284+
BoundConfiguration GetOrCreateBoundConfiguration(RequestConfiguration rc)
285+
{
286+
// Cache `BoundConfiguration` for requests with local request configuration.
287+
288+
// Since `IRequestConfiguration` might be implemented as mutable class, we use the
289+
// cache only with the immutable `RequestConfiguration` record.
290+
291+
// ReSharper disable InconsistentlySynchronizedField
292+
293+
var cache = (Interlocked.CompareExchange(
294+
ref _boundConfigurations,
295+
new ConditionalWeakTable<RequestConfiguration, BoundConfiguration>(),
296+
null
297+
) ?? _boundConfigurations)!;
298+
299+
if (cache.TryGetValue(rc, out var boundConfiguration))
300+
{
301+
return boundConfiguration;
302+
}
303+
304+
boundConfiguration = new BoundConfiguration(Configuration, rc);
305+
306+
#if NET8_0_OR_GREATER
307+
cache.TryAdd(rc, boundConfiguration);
308+
#else
309+
lock (cache)
310+
{
311+
cache.Add(rc, boundConfiguration);
312+
}
313+
#endif
314+
315+
// ReSharper restore InconsistentlySynchronizedField
316+
317+
return boundConfiguration;
318+
}
319+
}
320+
276321
private static void ThrowUnexpectedTransportException<TResponse>(Exception killerException,
277322
List<PipelineException> seenExceptions,
278323
Endpoint endpoint,

0 commit comments

Comments
 (0)