Skip to content

Commit cee5226

Browse files
authored
Reworked RequestContext pooling (#4141)
1 parent 2b5874e commit cee5226

File tree

3 files changed

+118
-59
lines changed

3 files changed

+118
-59
lines changed

src/HotChocolate/Core/src/Execution/DependencyInjection/InternalServiceCollectionExtensions.cs

+33-11
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using Microsoft.Extensions.Options;
66
using GreenDonut;
77
using HotChocolate.Execution;
8-
using HotChocolate.Execution.Batching;
98
using HotChocolate.Execution.Caching;
109
using HotChocolate.Execution.Configuration;
1110
using HotChocolate.Execution.Internal;
@@ -66,19 +65,17 @@ internal static IServiceCollection TryAddResolverTaskPool(
6665
}
6766

6867
internal static IServiceCollection TryAddOperationContextPool(
69-
this IServiceCollection services,
70-
int maximumRetained = -1)
68+
this IServiceCollection services)
7169
{
72-
if (maximumRetained < 1)
70+
services.TryAddSingleton<ObjectPool<OperationContext>>(sp =>
7371
{
74-
maximumRetained = Environment.ProcessorCount * 2;
75-
}
72+
ObjectPoolProvider provider = sp.GetRequiredService<ObjectPoolProvider>();
73+
var policy = new OperationContextPooledObjectPolicy(
74+
sp.GetRequiredService<ObjectPool<ResolverTask>>(),
75+
sp.GetRequiredService<ResultPool>());
76+
return provider.Create(policy);
77+
});
7678

77-
services.TryAddTransient<OperationContext>();
78-
services.TryAddSingleton<ObjectPool<OperationContext>>(
79-
sp => new DefaultObjectPool<OperationContext>(
80-
new OperationContextPoolPolicy(sp.GetRequiredService<OperationContext>),
81-
maximumRetained));
8279
return services;
8380
}
8481

@@ -192,5 +189,30 @@ internal static IServiceCollection AddParameterExpressionBuilder<T>(
192189
services.AddSingleton<IParameterExpressionBuilder>(factory);
193190
return services;
194191
}
192+
193+
private class OperationContextPooledObjectPolicy : PooledObjectPolicy<OperationContext>
194+
{
195+
private readonly ObjectPool<ResolverTask> _resolverTaskPool;
196+
private readonly ResultPool _resultPool;
197+
198+
public OperationContextPooledObjectPolicy(
199+
ObjectPool<ResolverTask> resolverTaskPool,
200+
ResultPool resultPool)
201+
{
202+
_resolverTaskPool = resolverTaskPool ??
203+
throw new ArgumentNullException(nameof(resolverTaskPool));
204+
_resultPool = resultPool ??
205+
throw new ArgumentNullException(nameof(resultPool));
206+
}
207+
208+
public override OperationContext Create()
209+
=> new(_resolverTaskPool, _resultPool);
210+
211+
public override bool Return(OperationContext obj)
212+
{
213+
obj.Clean();
214+
return true;
215+
}
216+
}
195217
}
196218
}

src/HotChocolate/Core/src/Execution/RequestExecutor.cs

+8-31
Original file line numberDiff line numberDiff line change
@@ -3,36 +3,27 @@
33
using System.Threading;
44
using System.Threading.Tasks;
55
using HotChocolate.Execution.Batching;
6-
using HotChocolate.Execution.Instrumentation;
7-
using HotChocolate.Execution.Processing;
8-
using HotChocolate.Utilities;
96
using Microsoft.Extensions.DependencyInjection;
7+
using Microsoft.Extensions.ObjectPool;
108

119
namespace HotChocolate.Execution
1210
{
1311
internal sealed class RequestExecutor : IRequestExecutor
1412
{
1513
private readonly DefaultRequestContextAccessor _requestContextAccessor;
1614
private readonly IServiceProvider _applicationServices;
17-
private readonly IErrorHandler _errorHandler;
18-
private readonly ITypeConverter _converter;
19-
private readonly IActivator _activator;
20-
private readonly IDiagnosticEvents _diagnosticEvents;
2115
private readonly RequestDelegate _requestDelegate;
2216
private readonly BatchExecutor _batchExecutor;
23-
private RequestContext? _pooledContext;
17+
private readonly ObjectPool<RequestContext> _contextPool;
2418

2519
public RequestExecutor(
2620
ISchema schema,
2721
DefaultRequestContextAccessor requestContextAccessor,
2822
IServiceProvider applicationServices,
2923
IServiceProvider executorServices,
30-
IErrorHandler errorHandler,
31-
ITypeConverter converter,
32-
IActivator activator,
33-
IDiagnosticEvents diagnosticEvents,
3424
RequestDelegate requestDelegate,
3525
BatchExecutor batchExecutor,
26+
ObjectPool<RequestContext> contextPool,
3627
ulong version)
3728
{
3829
Schema = schema ??
@@ -43,18 +34,12 @@ public RequestExecutor(
4334
throw new ArgumentNullException(nameof(applicationServices));
4435
Services = executorServices ??
4536
throw new ArgumentNullException(nameof(executorServices));
46-
_errorHandler = errorHandler ??
47-
throw new ArgumentNullException(nameof(errorHandler));
48-
_converter = converter ??
49-
throw new ArgumentNullException(nameof(converter));
50-
_activator = activator ??
51-
throw new ArgumentNullException(nameof(activator));
52-
_diagnosticEvents = diagnosticEvents ??
53-
throw new ArgumentNullException(nameof(diagnosticEvents));
5437
_requestDelegate = requestDelegate ??
5538
throw new ArgumentNullException(nameof(requestDelegate));
5639
_batchExecutor = batchExecutor ??
5740
throw new ArgumentNullException(nameof(batchExecutor));
41+
_contextPool = contextPool ??
42+
throw new ArgumentNullException(nameof(contextPool));
5843
Version = version;
5944
}
6045

@@ -81,18 +66,11 @@ public async Task<IExecutionResult> ExecuteAsync(
8166
? request.Services!
8267
: scope.ServiceProvider;
8368

84-
RequestContext? context = Interlocked.Exchange(ref _pooledContext, null);
69+
RequestContext context = _contextPool.Get();
8570

8671
try
8772
{
88-
context ??= new RequestContext(
89-
Schema,
90-
Version,
91-
_errorHandler,
92-
_converter,
93-
_activator,
94-
_diagnosticEvents) { RequestAborted = cancellationToken };
95-
73+
context.RequestAborted = cancellationToken;
9674
context.Initialize(request, services);
9775

9876
_requestContextAccessor.RequestContext = context;
@@ -124,8 +102,7 @@ public async Task<IExecutionResult> ExecuteAsync(
124102
}
125103
finally
126104
{
127-
context!.Reset();
128-
Interlocked.Exchange(ref _pooledContext, context);
105+
_contextPool.Return(context);
129106
scope?.Dispose();
130107
}
131108
}

src/HotChocolate/Core/src/Execution/RequestExecutorResolver.cs

+77-17
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,16 @@
1616
using HotChocolate.Types.Descriptors;
1717
using HotChocolate.Types.Descriptors.Definitions;
1818
using HotChocolate.Utilities;
19+
using Microsoft.Extensions.DependencyInjection.Extensions;
20+
using Microsoft.Extensions.ObjectPool;
1921
using static HotChocolate.Execution.ThrowHelper;
2022

2123
namespace HotChocolate.Execution
2224
{
2325
internal sealed class RequestExecutorResolver
2426
: IRequestExecutorResolver
25-
, IInternalRequestExecutorResolver
26-
, IDisposable
27+
, IInternalRequestExecutorResolver
28+
, IDisposable
2729
{
2830
private readonly SemaphoreSlim _semaphore = new(1, 1);
2931
private readonly ConcurrentDictionary<string, RegisteredExecutor> _executors = new();
@@ -39,9 +41,9 @@ public RequestExecutorResolver(
3941
IServiceProvider serviceProvider)
4042
{
4143
_optionsMonitor = optionsMonitor ??
42-
throw new ArgumentNullException(nameof(optionsMonitor));
44+
throw new ArgumentNullException(nameof(optionsMonitor));
4345
_applicationServices = serviceProvider ??
44-
throw new ArgumentNullException(nameof(serviceProvider));
46+
throw new ArgumentNullException(nameof(serviceProvider));
4547
_optionsMonitor.OnChange(EvictRequestExecutor);
4648
}
4749

@@ -145,8 +147,8 @@ private void BeginRunEvictionEvents(RegisteredExecutor registeredExecutor)
145147
if (action.AsyncAction is { } task)
146148
{
147149
await task.Invoke(
148-
registeredExecutor.Executor,
149-
CancellationToken.None)
150+
registeredExecutor.Executor,
151+
CancellationToken.None)
150152
.ConfigureAwait(false);
151153
}
152154
}
@@ -241,18 +243,30 @@ await CreateExecutorOptionsAsync(options, cancellationToken)
241243
_applicationServices.GetRequiredService<ITypeConverter>(),
242244
_applicationServices.GetRequiredService<InputFormatter>()));
243245

246+
serviceCollection.TryAddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();
247+
248+
serviceCollection.TryAddSingleton<ObjectPool<RequestContext>>(sp =>
249+
{
250+
ObjectPoolProvider provider = sp.GetRequiredService<ObjectPoolProvider>();
251+
var policy = new RequestContextPooledObjectPolicy(
252+
sp.GetRequiredService<ISchema>(),
253+
sp.GetRequiredService<IErrorHandler>(),
254+
_applicationServices.GetRequiredService<ITypeConverter>(),
255+
sp.GetRequiredService<IActivator>(),
256+
sp.GetRequiredService<IDiagnosticEvents>(),
257+
version);
258+
return provider.Create(policy);
259+
});
260+
244261
serviceCollection.AddSingleton<IRequestExecutor>(
245262
sp => new RequestExecutor(
246263
sp.GetRequiredService<ISchema>(),
247264
_applicationServices.GetRequiredService<DefaultRequestContextAccessor>(),
248265
_applicationServices,
249266
sp,
250-
sp.GetRequiredService<IErrorHandler>(),
251-
_applicationServices.GetRequiredService<ITypeConverter>(),
252-
sp.GetRequiredService<IActivator>(),
253-
sp.GetRequiredService<IDiagnosticEvents>(),
254267
sp.GetRequiredService<RequestDelegate>(),
255268
sp.GetRequiredService<BatchExecutor>(),
269+
sp.GetRequiredService<ObjectPool<RequestContext>>(),
256270
version));
257271

258272
foreach (Action<IServiceCollection> configureServices in options.SchemaServices)
@@ -265,12 +279,12 @@ await CreateExecutorOptionsAsync(options, cancellationToken)
265279

266280
lazy.Schema =
267281
await CreateSchemaAsync(
268-
schemaName,
269-
options,
270-
executorOptions,
271-
combinedServices,
272-
typeModuleChangeMonitor,
273-
cancellationToken)
282+
schemaName,
283+
options,
284+
executorOptions,
285+
combinedServices,
286+
typeModuleChangeMonitor,
287+
cancellationToken)
274288
.ConfigureAwait(false);
275289

276290
return schemaServices;
@@ -342,7 +356,7 @@ private static async ValueTask<RequestExecutorOptions> CreateExecutorOptionsAsyn
342356
CancellationToken cancellationToken)
343357
{
344358
RequestExecutorOptions executorOptions = options.RequestExecutorOptions ??
345-
new RequestExecutorOptions();
359+
new RequestExecutorOptions();
346360

347361
foreach (RequestExecutorOptionsAction action in options.RequestExecutorOptionsActions)
348362
{
@@ -531,5 +545,51 @@ await typeModule.CreateTypesAsync(_context, cancellationToken)
531545
}
532546
}
533547
}
548+
549+
private class RequestContextPooledObjectPolicy : PooledObjectPolicy<RequestContext>
550+
{
551+
private readonly ISchema _schema;
552+
private readonly ulong _executorVersion;
553+
private readonly IErrorHandler _errorHandler;
554+
private readonly ITypeConverter _converter;
555+
private readonly IActivator _activator;
556+
private readonly IDiagnosticEvents _diagnosticEvents;
557+
558+
public RequestContextPooledObjectPolicy(
559+
ISchema schema,
560+
IErrorHandler errorHandler,
561+
ITypeConverter converter,
562+
IActivator activator,
563+
IDiagnosticEvents diagnosticEvents,
564+
ulong executorVersion)
565+
{
566+
_schema = schema ??
567+
throw new ArgumentNullException(nameof(schema));
568+
_errorHandler = errorHandler ??
569+
throw new ArgumentNullException(nameof(errorHandler));
570+
_converter = converter ??
571+
throw new ArgumentNullException(nameof(converter));
572+
_activator = activator ??
573+
throw new ArgumentNullException(nameof(activator));
574+
_diagnosticEvents = diagnosticEvents ??
575+
throw new ArgumentNullException(nameof(diagnosticEvents));
576+
_executorVersion = executorVersion;
577+
}
578+
579+
580+
public override RequestContext Create()
581+
=> new(_schema,
582+
_executorVersion,
583+
_errorHandler,
584+
_converter,
585+
_activator,
586+
_diagnosticEvents);
587+
588+
public override bool Return(RequestContext obj)
589+
{
590+
obj.Reset();
591+
return true;
592+
}
593+
}
534594
}
535595
}

0 commit comments

Comments
 (0)