Skip to content

Commit

Permalink
refinements
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelstaib committed Mar 19, 2024
1 parent fd1e64e commit dfcd21a
Show file tree
Hide file tree
Showing 15 changed files with 267 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using HotChocolate.Execution.DependencyInjection;
using HotChocolate.Execution.Processing;
using HotChocolate.Execution.Processing.Tasks;
using HotChocolate.Resolvers;
using HotChocolate.Utilities;

namespace Microsoft.Extensions.DependencyInjection;
Expand All @@ -17,28 +18,17 @@ namespace Microsoft.Extensions.DependencyInjection;
///
/// The <see cref="OperationContextFactory"/> MUST be a singleton.
/// </summary>
internal sealed class OperationContextFactory : IFactory<OperationContext>
internal sealed class OperationContextFactory(
IFactory<ResolverTask> resolverTaskFactory,
ResultPool resultPool,
ITypeConverter typeConverter,
AggregateServiceScopeInitializer serviceScopeInitializer)
: IFactory<OperationContext>
{
private readonly IFactory<ResolverTask> _resolverTaskFactory;
private readonly ResultPool _resultPool;
private readonly ITypeConverter _typeConverter;

public OperationContextFactory(
IFactory<ResolverTask> resolverTaskFactory,
ResultPool resultPool,
ITypeConverter typeConverter)
{
_resolverTaskFactory = resolverTaskFactory ??
throw new ArgumentNullException(nameof(resolverTaskFactory));
_resultPool = resultPool ??
throw new ArgumentNullException(nameof(resultPool));
_typeConverter = typeConverter ??
throw new ArgumentNullException(nameof(typeConverter));
}

public OperationContext Create()
=> new OperationContext(
_resolverTaskFactory,
new ResultBuilder(_resultPool),
_typeConverter);
resolverTaskFactory,
new ResultBuilder(resultPool),
typeConverter,
serviceScopeInitializer);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System;
using HotChocolate.Execution.Configuration;
using HotChocolate.Resolvers;

// ReSharper disable once CheckNamespace
namespace Microsoft.Extensions.DependencyInjection;

public static partial class RequestExecutorBuilderExtensions
{
/// <summary>
/// Adds a initializer to copy state between a request scoped service instance and
/// a resolver scoped service instance.
/// </summary>
/// <param name="builder">
/// The request executor builder.
/// </param>
/// <param name="initializer">
/// The initializer that is used to initialize the service scope.
/// </param>
/// <typeparam name="TService">
/// The type of the service that shall be initialized.
/// </typeparam>
/// <returns></returns>
/// <exception cref="ArgumentNullException">
/// The <paramref name="builder"/> is <c>null</c>.
/// </exception>
public static IRequestExecutorBuilder AddScopedServiceInitializer<TService>(
this IRequestExecutorBuilder builder,
Action<TService, TService> initializer)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}

if (initializer == null)
{
throw new ArgumentNullException(nameof(initializer));
}

builder.Services.AddSingleton<IServiceScopeInitializer>(new DelegateServiceInitializer<TService>(initializer));
return builder;
}
}

file sealed class DelegateServiceInitializer<TService>(
Action<TService, TService> initializer)
: ServiceInitializer<TService>
{
private readonly Action<TService, TService> _initializer = initializer ??
throw new ArgumentNullException(nameof(initializer));

protected override void Initialize(TService requestScopeService, TService resolverScopeService)
=> _initializer(requestScopeService, resolverScopeService);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using HotChocolate.Execution.Processing;
using HotChocolate.Fetching;
using HotChocolate.Language;
using HotChocolate.Resolvers;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.ObjectPool;

Expand All @@ -36,6 +37,7 @@ public static IServiceCollection AddGraphQLCore(this IServiceCollection services
services.TryAddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();
services.TryAddSingleton<DefaultRequestContextAccessor>();
services.TryAddSingleton<IRequestContextAccessor>(sp => sp.GetRequiredService<DefaultRequestContextAccessor>());
services.TryAddSingleton<AggregateServiceScopeInitializer>();

services.TryAddSingleton<ObjectPool<StringBuilder>>(sp =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,15 @@
<Compile Update="Batching\BatchExecutor.Enumerable.cs">
<DependentUpon>BatchExecutor.cs</DependentUpon>
</Compile>
<Compile Update="DependencyInjection\RequestExecutorBuilderExtensions.InputParser.cs">
<DependentUpon>RequestExecutorBuilderExtensions.cs</DependentUpon>
</Compile>
<Compile Update="DependencyInjection\RequestExecutorBuilderExtensions.TypeDiscovery.cs">
<DependentUpon>RequestExecutorBuilderExtensions.cs</DependentUpon>
</Compile>
<Compile Update="DependencyInjection\RequestExecutorBuilderExtensions.Services.cs">
<DependentUpon>RequestExecutorBuilderExtensions.cs</DependentUpon>
</Compile>
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using HotChocolate.Execution.Instrumentation;
using HotChocolate.Execution.Processing.Tasks;
using HotChocolate.Fetching;
using HotChocolate.Resolvers;
using HotChocolate.Types;
using HotChocolate.Utilities;
using Microsoft.Extensions.DependencyInjection;
Expand All @@ -19,6 +20,7 @@ internal sealed partial class OperationContext
private readonly WorkScheduler _workScheduler;
private readonly DeferredWorkScheduler _deferredWorkScheduler;
private readonly ResultBuilder _resultBuilder;
private readonly AggregateServiceScopeInitializer _serviceScopeInitializer;
private IRequestContext _requestContext = default!;
private ISchema _schema = default!;
private IErrorHandler _errorHandler = default!;
Expand All @@ -38,12 +40,14 @@ internal sealed partial class OperationContext
public OperationContext(
IFactory<ResolverTask> resolverTaskFactory,
ResultBuilder resultBuilder,
ITypeConverter typeConverter)
ITypeConverter typeConverter,
AggregateServiceScopeInitializer serviceScopeInitializer)
{
_resolverTaskFactory = resolverTaskFactory;
_workScheduler = new(this);
_deferredWorkScheduler = new();
_workScheduler = new WorkScheduler(this);
_deferredWorkScheduler = new DeferredWorkScheduler();
_resultBuilder = resultBuilder;
_serviceScopeInitializer = serviceScopeInitializer;
Converter = typeConverter;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using HotChocolate.Resolvers;
using HotChocolate.Types;

namespace HotChocolate.Execution.Processing;
Expand All @@ -22,4 +23,9 @@ internal sealed partial class OperationContext
/// Gets access to the input parser.
/// </summary>
public InputParser InputParser => _inputParser;

/// <summary>
/// Gets the service scope initializer.
/// </summary>
public AggregateServiceScopeInitializer ServiceScopeInitializer => _serviceScopeInitializer;
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
#if NET6_0_OR_GREATER
using System.Runtime.InteropServices;
#endif
using System.Threading;
using System.Threading.Tasks;
using HotChocolate.Execution.Internal;
using HotChocolate.Resolvers;
using HotChocolate.Types;
using Microsoft.Extensions.DependencyInjection;

Expand Down Expand Up @@ -71,7 +72,7 @@ private async Task ExecuteAsync(CancellationToken cancellationToken)
await _context.ExecuteCleanupTasksAsync().ConfigureAwait(false);
}

_objectPool.Return(this);
objectPool.Return(this);
}
}

Expand Down Expand Up @@ -142,6 +143,7 @@ private async ValueTask ExecuteResolverPipelineAsync(CancellationToken cancellat
var serviceScope = _operationContext.Services.CreateAsyncScope();
_context.Services = serviceScope.ServiceProvider;
_context.RegisterForCleanup(serviceScope.DisposeAsync);
_operationContext.ServiceScopeInitializer.Initialize(_context.RequestServices, _context.Services);
}

await _context.ResolverPipeline!(_context).ConfigureAwait(false);
Expand Down Expand Up @@ -304,7 +306,7 @@ public async ValueTask CompleteUnsafeAsync()

Status = _completionStatus;
_operationContext.Scheduler.Complete(this);
_objectPool.Return(this);
objectPool.Return(this);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@

namespace HotChocolate.Execution.Processing.Tasks;

internal sealed partial class ResolverTask : IExecutionTask
internal sealed partial class ResolverTask(ObjectPool<ResolverTask> objectPool) : IExecutionTask
{
private readonly ObjectPool<ResolverTask> _objectPool;
private readonly MiddlewareContext _context = new();
private readonly List<ResolverTask> _taskBuffer = [];
private readonly Dictionary<string, ArgumentValue> _args = new(StringComparer.Ordinal);
Expand All @@ -19,11 +18,6 @@ internal sealed partial class ResolverTask : IExecutionTask
private ExecutionTaskStatus _completionStatus = ExecutionTaskStatus.Completed;
private Task? _task;

public ResolverTask(ObjectPool<ResolverTask> objectPool)
{
_objectPool = objectPool ?? throw new ArgumentNullException(nameof(objectPool));
}

/// <summary>
/// Gets access to the resolver context for this task.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace HotChocolate.Resolvers;

internal sealed class AggregateServiceScopeInitializer : IServiceScopeInitializer
{
private readonly IServiceScopeInitializer[] _initializers;

public AggregateServiceScopeInitializer(IEnumerable<IServiceScopeInitializer> serviceScopeInitializers)
{
if (serviceScopeInitializers == null)
{
throw new ArgumentNullException(nameof(serviceScopeInitializers));
}

_initializers = serviceScopeInitializers.ToArray();
}

public void Initialize(IServiceProvider requestScope, IServiceProvider resolverScope)
{
switch (_initializers.Length)
{
case 0:
return;

case 1:
_initializers[0].Initialize(requestScope, resolverScope);
break;

case 2:
_initializers[0].Initialize(requestScope, resolverScope);
_initializers[1].Initialize(requestScope, resolverScope);
break;

case 3:
_initializers[0].Initialize(requestScope, resolverScope);
_initializers[1].Initialize(requestScope, resolverScope);
_initializers[2].Initialize(requestScope, resolverScope);
break;

default:
{
ref var start = ref MemoryMarshal.GetReference(_initializers.AsSpan());
ref var end = ref Unsafe.Add(ref start, _initializers.Length);

while (Unsafe.IsAddressLessThan(ref start, ref end))
{
start.Initialize(requestScope, resolverScope);
start = ref Unsafe.Add(ref start, 1);
}
break;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;

namespace HotChocolate.Resolvers;

/// <summary>
/// Implement this to copy state from the requestScope to the resolver scope.
/// </summary>
public interface IServiceScopeInitializer
{
/// <summary>
/// Initializes the resolver scope with state from the request scope.
/// </summary>
/// <param name="requestScope">
/// The request scope from which state shall be copied.
/// </param>
/// <param name="resolverScope">
/// The resolver scope to which state shall be copied.
/// </param>
void Initialize(IServiceProvider requestScope, IServiceProvider resolverScope);
}
18 changes: 18 additions & 0 deletions src/HotChocolate/Core/src/Types/Resolvers/ServiceInitializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;

namespace HotChocolate.Resolvers;

/// <summary>
/// This is a base class allows to copy state from a single service of the
/// request scope to the same service in the resolver scope.
/// </summary>
/// <typeparam name="TService">
/// The service type for which state shall be copied.
/// </typeparam>
public abstract class ServiceInitializer<TService> : IServiceScopeInitializer
{
public void Initialize(IServiceProvider requestScope, IServiceProvider resolverScope)
=> Initialize(requestScope.GetRequiredService<TService>(), resolverScope.GetRequiredService<TService>());

protected abstract void Initialize(TService requestScopeService, TService resolverScopeService);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using HotChocolate.Execution.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Moq;

namespace HotChocolate.Execution.DependencyInjection;

public class RequestExecutorBuilderExtensionsServicesTests
{
[Fact]
public void AddScopedServiceInitializer_1_Builder_Is_Null()
{
void Fail() => RequestExecutorBuilderExtensions
.AddScopedServiceInitializer<string>(null!, (_, _) => { });

Assert.Throws<ArgumentNullException>(Fail);
}

[Fact]
public void AddScopedServiceInitializer_1_Initializer_Is_Null()
{
var mock = new Mock<IRequestExecutorBuilder>();

void Fail() => RequestExecutorBuilderExtensions
.AddScopedServiceInitializer<string>(mock.Object, null!);

Assert.Throws<ArgumentNullException>(Fail);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -217,9 +217,7 @@ void Fail()
Assert.Throws<ArgumentNullException>(Fail);
}

public class MockVisitor : DocumentValidatorVisitor
{
}
public class MockVisitor : DocumentValidatorVisitor;

public class MockRule : IDocumentValidatorRule
{
Expand Down
Loading

0 comments on commit dfcd21a

Please sign in to comment.