diff --git a/.devops/azure-pipelines.release-hotchocolate.yml b/.devops/azure-pipelines.release-hotchocolate.yml
index 81458c77815..0ac2fe415ec 100644
--- a/.devops/azure-pipelines.release-hotchocolate.yml
+++ b/.devops/azure-pipelines.release-hotchocolate.yml
@@ -1,3 +1,4 @@
+--- # ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
#
#
@@ -12,60 +13,59 @@
# nuke --generate-configuration AzurePipelines_test-pr-hotchocolate --host AzurePipelines
#
#
-# ------------------------------------------------------------------------------
trigger: none
stages:
- stage: ubuntu_latest
- displayName: 'ubuntu-latest'
- dependsOn: [ ]
+ displayName: "ubuntu-latest"
+ dependsOn: []
pool:
- vmImage: 'ubuntu-latest'
+ vmImage: "ubuntu-latest"
jobs:
- job: Test
- displayName: 'Test'
- dependsOn: [ ]
+ displayName: "Test"
+ dependsOn: []
strategy:
parallel: 8
- steps:
+ steps:
- task: CmdLine@2
inputs:
- script: './build.cmd test --skip --test-partition $(System.JobPositionInPhase)'
+ script: "./build.cmd test --skip --test-partition $(System.JobPositionInPhase)"
- task: PublishBuildArtifacts@1
inputs:
artifactName: test-results
- pathtoPublish: 'output/test-results'
+ pathtoPublish: "output/test-results"
- job: Pack
- displayName: 'Pack'
- dependsOn: [ ]
- steps:
+ displayName: "Pack"
+ dependsOn: []
+ steps:
- task: CmdLine@2
inputs:
- script: './build.cmd pack --skip'
+ script: "./build.cmd pack --skip"
- task: PublishBuildArtifacts@1
inputs:
artifactName: packages
- pathtoPublish: 'output/packages'
+ pathtoPublish: "output/packages"
- job: Publish
- displayName: 'Publish'
- dependsOn: [ Test, Pack ]
+ displayName: "Publish"
+ dependsOn: [Test, Pack]
steps:
- task: DownloadBuildArtifacts@0
inputs:
- artifactName: 'packages'
- downloadPath: '$(Build.Repository.LocalPath)/output'
+ artifactName: "packages"
+ downloadPath: "$(Build.Repository.LocalPath)/output"
- task: CmdLine@2
inputs:
- script: './build.cmd publish --skip'
+ script: "./build.cmd publish --skip"
- task: GitHubRelease@0
- displayName: 'GitHub release'
+ displayName: "GitHub release"
inputs:
- gitHubConnection: 'ChilliCream GitHub'
+ gitHubConnection: "ChilliCream GitHub"
action: edit
- tag: '$(GitHubVersion)'
- title: '$(GitHubVersion)'
- assets: '$(Build.Repository.LocalPath)/output/packages/*.*'
+ tag: "$(GitHubVersion)"
+ title: "$(GitHubVersion)"
+ assets: "$(Build.Repository.LocalPath)/output/packages/*.*"
assetUploadMode: replace
isPreRelease: true
releaseNotesSource: input
- releaseNotes: 'For more details click [here](https://github.com/ChilliCream/hotchocolate/blob/master/CHANGELOG.md) to get to our CHANGELOG.'
+ releaseNotes: "For more details click [here](https://github.com/ChilliCream/hotchocolate/blob/master/CHANGELOG.md) to get to our CHANGELOG."
diff --git a/build.ps1 b/build.ps1
index ac154ae91f4..f9830771a0f 100644
--- a/build.ps1
+++ b/build.ps1
@@ -55,6 +55,10 @@ else {
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
(New-Object System.Net.WebClient).DownloadFile($DotNetInstallUrl, $DotNetInstallFile)
+ ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Version "2.1.809" -NoPath }
+ ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Version "3.0.103" -NoPath }
+ ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Version "3.1.401" -NoPath }
+
# Install by channel or version
if (!(Test-Path variable:DotNetVersion)) {
ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Channel $DotNetChannel -NoPath }
diff --git a/build.sh b/build.sh
index 7cee9063cf2..2b4db617e74 100755
--- a/build.sh
+++ b/build.sh
@@ -48,6 +48,10 @@ else
curl -Lsfo "$DOTNET_INSTALL_FILE" "$DOTNET_INSTALL_URL"
chmod +x "$DOTNET_INSTALL_FILE"
+ "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --version "2.1.809" --no-path
+ "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --version "3.0.103" --no-path
+ "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --version "3.1.401" --no-path
+
# Install by channel or version
if [[ -z ${DOTNET_VERSION+x} ]]; then
"$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --channel "$DOTNET_CHANNEL" --no-path
diff --git a/global.json b/global.json
index 0ebfb85f9ab..f8971bd5559 100644
--- a/global.json
+++ b/global.json
@@ -1,5 +1,5 @@
{
"sdk": {
- "version": "3.1.300"
+ "version": "5.0.100-preview.7.20366.6"
}
}
diff --git a/src/GreenDonut/src/Core/Batch.cs b/src/GreenDonut/src/Core/Batch.cs
index 770b8af0ade..29f22afce3d 100644
--- a/src/GreenDonut/src/Core/Batch.cs
+++ b/src/GreenDonut/src/Core/Batch.cs
@@ -2,36 +2,50 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
+using System.Diagnostics.CodeAnalysis;
namespace GreenDonut
{
public class Batch
where TKey : notnull
{
+ private readonly object _sync = new object();
+ private bool _hasDispatched = false;
private Dictionary> _items =
new Dictionary>();
- public bool HasDispatched { get; private set; }
-
public IReadOnlyList Keys => _items.Keys.ToArray();
public int Size => _items.Count;
- public TaskCompletionSource CreateOrGet(TKey key)
+ public bool TryGetOrCreate(
+ TKey key,
+ [NotNullWhen(true)] out TaskCompletionSource? promise)
{
- ThrowIfDispatched();
-
- if (_items.ContainsKey(key))
+ if (!_hasDispatched)
{
- return _items[key];
- }
+ lock (_sync)
+ {
+ if (!_hasDispatched)
+ {
+ if (_items.ContainsKey(key))
+ {
+ promise = _items[key];
+ }
+ else
+ {
+ promise = new TaskCompletionSource(
+ TaskCreationOptions.RunContinuationsAsynchronously);
+ _items.Add(key, promise);
+ }
- var promise = new TaskCompletionSource(
- TaskCreationOptions.RunContinuationsAsynchronously);
-
- _items.Add(key, promise);
+ return true;
+ }
+ }
+ }
- return promise;
+ promise = null;
+ return false;
}
public TaskCompletionSource Get(TKey key)
@@ -39,19 +53,27 @@ public TaskCompletionSource Get(TKey key)
return _items[key];
}
- public void StartDispatching()
+ public ValueTask StartDispatchingAsync(Func dispatch)
{
- ThrowIfDispatched();
+ bool execute = false;
- HasDispatched = true;
- }
+ if (!_hasDispatched)
+ {
+ lock (_sync)
+ {
+ if (!_hasDispatched)
+ {
+ execute = _hasDispatched = true;
+ }
+ }
+ }
- private void ThrowIfDispatched()
- {
- if (HasDispatched)
+ if (execute)
{
- throw new InvalidOperationException("This batch has already been dispatched.");
+ return dispatch();
}
+
+ return default;
}
}
}
diff --git a/src/GreenDonut/src/Core/DataLoaderBase.cs b/src/GreenDonut/src/Core/DataLoaderBase.cs
index 639709b9966..ca111745deb 100644
--- a/src/GreenDonut/src/Core/DataLoaderBase.cs
+++ b/src/GreenDonut/src/Core/DataLoaderBase.cs
@@ -174,7 +174,7 @@ public void Clear()
/// A list of results which are in the exact same order as the provided
/// keys.
///
- protected abstract Task>> FetchAsync(
+ protected abstract ValueTask>> FetchAsync(
IReadOnlyList keys,
CancellationToken cancellationToken);
@@ -189,7 +189,6 @@ public Task LoadAsync(TKey key, CancellationToken cancellationToken)
lock (_sync)
{
object cacheKey = _cacheKeyResolver(key);
- Batch batch = GetCurrentBatch();
if (_options.Caching && _cache.TryGetValue(cacheKey, out object? cachedValue))
{
@@ -200,7 +199,7 @@ public Task LoadAsync(TKey key, CancellationToken cancellationToken)
return cachedTask;
}
- TaskCompletionSource promise = batch.CreateOrGet(key);
+ TaskCompletionSource promise = GetOrCreatePromise(key);
if (_options.Caching)
{
@@ -312,62 +311,48 @@ private void BatchOperationSucceeded(
}
}
- private Task DispatchBatchAsync(
+ private ValueTask DispatchBatchAsync(
Batch batch,
CancellationToken cancellationToken)
{
- if (!batch.HasDispatched)
+ return batch.StartDispatchingAsync(async () =>
{
- lock (_sync)
- {
- if (!batch.HasDispatched)
- {
- batch.StartDispatching();
+ Activity? activity = DiagnosticEvents.StartBatching(batch.Keys);
+ IReadOnlyList> results = new Result[0];
- return DispatchBatchInternalAsync(batch, batch.Keys, cancellationToken);
- }
+ try
+ {
+ results = await FetchAsync(batch.Keys, cancellationToken).ConfigureAwait(false);
+ BatchOperationSucceeded(batch, batch.Keys, results);
+ }
+ catch (Exception ex)
+ {
+ BatchOperationFailed(batch, batch.Keys, ex);
}
- }
- return Task.CompletedTask;
+ DiagnosticEvents.StopBatching(activity, batch.Keys,
+ results.Select(result => result.Value).ToArray());
+ });
}
- private async Task DispatchBatchInternalAsync(
- Batch batch,
- IReadOnlyList keys,
- CancellationToken cancellationToken)
+ private TaskCompletionSource GetOrCreatePromise(TKey key)
{
- Activity? activity = DiagnosticEvents.StartBatching(keys);
- IReadOnlyList> results = new Result[0];
-
- try
- {
- results = await FetchAsync(keys, cancellationToken).ConfigureAwait(false);
- BatchOperationSucceeded(batch, keys, results);
- }
- catch (Exception ex)
+ if (_currentBatch is {} &&
+ _currentBatch.Size < _maxBatchSize &&
+ _currentBatch.TryGetOrCreate(key, out TaskCompletionSource? promise) &&
+ promise is {})
{
- BatchOperationFailed(batch, keys, ex);
+ return promise;
}
- DiagnosticEvents.StopBatching(activity, keys,
- results.Select(result => result.Value).ToArray());
- }
-
- private Batch GetCurrentBatch()
- {
- if (_currentBatch == null ||
- _currentBatch.HasDispatched ||
- _currentBatch.Size == _maxBatchSize)
- {
- var newBatch = new Batch();
+ var newBatch = new Batch();
- _batchScheduler.Schedule(() =>
- DispatchBatchAsync(newBatch, _disposeTokenSource.Token));
- _currentBatch = newBatch;
- }
+ newBatch.TryGetOrCreate(key, out TaskCompletionSource? newPromise);
+ _batchScheduler.Schedule(() =>
+ DispatchBatchAsync(newBatch, _disposeTokenSource.Token));
+ _currentBatch = newBatch;
- return _currentBatch;
+ return newPromise!;
}
private async Task> LoadInternalAsync(
diff --git a/src/GreenDonut/src/Core/FetchDataDelegate.cs b/src/GreenDonut/src/Core/FetchDataDelegate.cs
index 13198eccd82..db6d5b12ae2 100644
--- a/src/GreenDonut/src/Core/FetchDataDelegate.cs
+++ b/src/GreenDonut/src/Core/FetchDataDelegate.cs
@@ -17,7 +17,7 @@ namespace GreenDonut
/// A list of results which are in the exact same order as the provided
/// keys.
///
- public delegate Task>> FetchDataDelegate(
+ public delegate ValueTask>> FetchDataDelegate(
IReadOnlyList keys,
CancellationToken cancellationToken)
where TKey : notnull;
diff --git a/src/GreenDonut/src/Core/GreenDonut.csproj b/src/GreenDonut/src/Core/GreenDonut.csproj
index ec968b9b495..ea35630654d 100644
--- a/src/GreenDonut/src/Core/GreenDonut.csproj
+++ b/src/GreenDonut/src/Core/GreenDonut.csproj
@@ -9,6 +9,7 @@
+
diff --git a/src/GreenDonut/src/Core/IBatchScheduler.cs b/src/GreenDonut/src/Core/IBatchScheduler.cs
index 23768d4cdc5..0bf62f1ef19 100644
--- a/src/GreenDonut/src/Core/IBatchScheduler.cs
+++ b/src/GreenDonut/src/Core/IBatchScheduler.cs
@@ -1,9 +1,10 @@
using System;
+using System.Threading.Tasks;
namespace GreenDonut
{
public interface IBatchScheduler
{
- void Schedule(Action dispatch);
+ void Schedule(Func dispatch);
}
}
diff --git a/src/GreenDonut/test/Core.Tests/DataLoader.cs b/src/GreenDonut/test/Core.Tests/DataLoader.cs
index e648c07bb52..20e939781ff 100644
--- a/src/GreenDonut/test/Core.Tests/DataLoader.cs
+++ b/src/GreenDonut/test/Core.Tests/DataLoader.cs
@@ -24,7 +24,7 @@ public DataLoader(
_fetch = fetch ?? throw new ArgumentNullException(nameof(fetch));
}
- protected override Task>> FetchAsync(
+ protected override ValueTask>> FetchAsync(
IReadOnlyList keys,
CancellationToken cancellationToken)
{
diff --git a/src/GreenDonut/test/Core.Tests/ManualBatchScheduler.cs b/src/GreenDonut/test/Core.Tests/ManualBatchScheduler.cs
index 1313ae02da4..20f4a390cba 100644
--- a/src/GreenDonut/test/Core.Tests/ManualBatchScheduler.cs
+++ b/src/GreenDonut/test/Core.Tests/ManualBatchScheduler.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Concurrent;
+using System.Threading.Tasks;
namespace GreenDonut
{
@@ -7,20 +8,21 @@ public class ManualBatchScheduler
: IBatchScheduler
{
private readonly object _sync = new object();
- private readonly ConcurrentQueue _queue = new ConcurrentQueue();
+ private readonly ConcurrentQueue> _queue =
+ new ConcurrentQueue>();
public void Dispatch()
{
lock(_sync)
{
- while (_queue.TryDequeue(out Action dispatch))
+ while (_queue.TryDequeue(out Func dispatch))
{
dispatch();
}
}
}
- public void Schedule(Action dispatch)
+ public void Schedule(Func dispatch)
{
_queue.Enqueue(dispatch);
}
diff --git a/src/GreenDonut/test/DiagnosticSource.Tests/DataLoader.cs b/src/GreenDonut/test/DiagnosticSource.Tests/DataLoader.cs
index e648c07bb52..20e939781ff 100644
--- a/src/GreenDonut/test/DiagnosticSource.Tests/DataLoader.cs
+++ b/src/GreenDonut/test/DiagnosticSource.Tests/DataLoader.cs
@@ -24,7 +24,7 @@ public DataLoader(
_fetch = fetch ?? throw new ArgumentNullException(nameof(fetch));
}
- protected override Task>> FetchAsync(
+ protected override ValueTask>> FetchAsync(
IReadOnlyList keys,
CancellationToken cancellationToken)
{
diff --git a/src/GreenDonut/test/DiagnosticSource.Tests/DiagnosticEventsTests.cs b/src/GreenDonut/test/DiagnosticSource.Tests/DiagnosticEventsTests.cs
index 766d7b908da..81f727961d0 100644
--- a/src/GreenDonut/test/DiagnosticSource.Tests/DiagnosticEventsTests.cs
+++ b/src/GreenDonut/test/DiagnosticSource.Tests/DiagnosticEventsTests.cs
@@ -51,7 +51,7 @@ public async Task VerifyEvents()
}
}
- private async Task>> FetchDataAsync(
+ private ValueTask>> FetchDataAsync(
IReadOnlyList keys,
CancellationToken cancellationToken)
{
@@ -65,7 +65,7 @@ private async Task>> FetchDataAsync(
: Result.Resolve(null);
}
- return await Task.FromResult(results).ConfigureAwait(false);
+ return new ValueTask>>(results);
}
private async Task Catch(Func execute)
diff --git a/src/GreenDonut/test/DiagnosticSource.Tests/ManualBatchScheduler.cs b/src/GreenDonut/test/DiagnosticSource.Tests/ManualBatchScheduler.cs
index 1313ae02da4..20f4a390cba 100644
--- a/src/GreenDonut/test/DiagnosticSource.Tests/ManualBatchScheduler.cs
+++ b/src/GreenDonut/test/DiagnosticSource.Tests/ManualBatchScheduler.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Concurrent;
+using System.Threading.Tasks;
namespace GreenDonut
{
@@ -7,20 +8,21 @@ public class ManualBatchScheduler
: IBatchScheduler
{
private readonly object _sync = new object();
- private readonly ConcurrentQueue _queue = new ConcurrentQueue();
+ private readonly ConcurrentQueue> _queue =
+ new ConcurrentQueue>();
public void Dispatch()
{
lock(_sync)
{
- while (_queue.TryDequeue(out Action dispatch))
+ while (_queue.TryDequeue(out Func dispatch))
{
dispatch();
}
}
}
- public void Schedule(Action dispatch)
+ public void Schedule(Func dispatch)
{
_queue.Enqueue(dispatch);
}
diff --git a/src/HotChocolate/Core/HotChocolate.Core.sln b/src/HotChocolate/Core/HotChocolate.Core.sln
index 4dcff98d402..d01fce63c29 100644
--- a/src/HotChocolate/Core/HotChocolate.Core.sln
+++ b/src/HotChocolate/Core/HotChocolate.Core.sln
@@ -65,6 +65,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.Subscriptions.
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.Subscriptions.InMemory.Tests", "test\Subscriptions.InMemory.Tests\HotChocolate.Subscriptions.InMemory.Tests.csproj", "{F72E72DE-F163-4BCC-A442-94DB331D3C35}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.Fetching.Tests", "test\Fetching.Tests\HotChocolate.Fetching.Tests.csproj", "{2A319702-6C2E-4184-8E71-43F87745B38D}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -399,6 +401,18 @@ Global
{F72E72DE-F163-4BCC-A442-94DB331D3C35}.Release|x64.Build.0 = Release|Any CPU
{F72E72DE-F163-4BCC-A442-94DB331D3C35}.Release|x86.ActiveCfg = Release|Any CPU
{F72E72DE-F163-4BCC-A442-94DB331D3C35}.Release|x86.Build.0 = Release|Any CPU
+ {2A319702-6C2E-4184-8E71-43F87745B38D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2A319702-6C2E-4184-8E71-43F87745B38D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2A319702-6C2E-4184-8E71-43F87745B38D}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {2A319702-6C2E-4184-8E71-43F87745B38D}.Debug|x64.Build.0 = Debug|Any CPU
+ {2A319702-6C2E-4184-8E71-43F87745B38D}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {2A319702-6C2E-4184-8E71-43F87745B38D}.Debug|x86.Build.0 = Debug|Any CPU
+ {2A319702-6C2E-4184-8E71-43F87745B38D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2A319702-6C2E-4184-8E71-43F87745B38D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2A319702-6C2E-4184-8E71-43F87745B38D}.Release|x64.ActiveCfg = Release|Any CPU
+ {2A319702-6C2E-4184-8E71-43F87745B38D}.Release|x64.Build.0 = Release|Any CPU
+ {2A319702-6C2E-4184-8E71-43F87745B38D}.Release|x86.ActiveCfg = Release|Any CPU
+ {2A319702-6C2E-4184-8E71-43F87745B38D}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -431,6 +445,7 @@ Global
{3ED68361-0D3D-45F5-8A83-444DD3D5C1BF} = {37B9D3B1-CA34-4720-9A0B-CFF1E64F52C2}
{14648C7D-8958-4C94-B837-7F90F2F10081} = {7462D089-D350-44D6-8131-896D949A65B7}
{F72E72DE-F163-4BCC-A442-94DB331D3C35} = {7462D089-D350-44D6-8131-896D949A65B7}
+ {2A319702-6C2E-4184-8E71-43F87745B38D} = {7462D089-D350-44D6-8131-896D949A65B7}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E4D94C77-6657-4630-9D42-0A9AC5153A1B}
diff --git a/src/HotChocolate/Core/src/Abstractions/Error.cs b/src/HotChocolate/Core/src/Abstractions/Error.cs
index 291fb0fa4ba..882986678cc 100644
--- a/src/HotChocolate/Core/src/Abstractions/Error.cs
+++ b/src/HotChocolate/Core/src/Abstractions/Error.cs
@@ -3,7 +3,7 @@
using HotChocolate.Execution;
using HotChocolate.Properties;
-#nullable enable
+#nullable enable
namespace HotChocolate
{
@@ -33,9 +33,9 @@ public Error(
Extensions = extensions;
Exception = exception;
- if (code is { } && Extensions is null)
+ if (code is not null && Extensions is null)
{
- Extensions = new OrderedDictionary { {_codePropertyName, code} };
+ Extensions = new OrderedDictionary { { _codePropertyName, code } };
}
}
@@ -71,8 +71,8 @@ public IError WithCode(string? code)
}
var extensions = Extensions is null
- ? new OrderedDictionary() {[_codePropertyName] = code}
- : new OrderedDictionary(Extensions) {[_codePropertyName] = code};
+ ? new OrderedDictionary() { [_codePropertyName] = code }
+ : new OrderedDictionary(Extensions) { [_codePropertyName] = code };
return new Error(Message, code, Path, Locations, extensions, Exception);
}
diff --git a/src/HotChocolate/Core/src/Execution/Utilities/ITask.cs b/src/HotChocolate/Core/src/Abstractions/Execution/IExecutionTask.cs
similarity index 70%
rename from src/HotChocolate/Core/src/Execution/Utilities/ITask.cs
rename to src/HotChocolate/Core/src/Abstractions/Execution/IExecutionTask.cs
index a69de236f23..1e7eef5e54e 100644
--- a/src/HotChocolate/Core/src/Execution/Utilities/ITask.cs
+++ b/src/HotChocolate/Core/src/Abstractions/Execution/IExecutionTask.cs
@@ -1,10 +1,12 @@
-namespace HotChocolate.Execution.Utilities
+namespace HotChocolate.Execution
{
///
/// Represents a task that shall be executed by the execution engine.
///
- internal interface ITask
+ public interface IExecutionTask
{
+ bool IsCompleted { get; }
+
///
/// Starts the execution of this task.
///
diff --git a/src/HotChocolate/Core/src/Abstractions/Execution/IExecutionTaskContext.cs b/src/HotChocolate/Core/src/Abstractions/Execution/IExecutionTaskContext.cs
new file mode 100644
index 00000000000..1d0e7f4115f
--- /dev/null
+++ b/src/HotChocolate/Core/src/Abstractions/Execution/IExecutionTaskContext.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace HotChocolate.Execution
+{
+ public interface IExecutionTaskContext
+ {
+ void ReportError(IExecutionTask task, IError error);
+
+ void ReportError(IExecutionTask task, Exception exception);
+
+ IDisposable Track(IExecutionTask task);
+
+ void Started();
+
+ void Completed();
+ }
+}
diff --git a/src/HotChocolate/Core/src/Abstractions/Execution/IExecutionTaskDefinition.cs b/src/HotChocolate/Core/src/Abstractions/Execution/IExecutionTaskDefinition.cs
new file mode 100644
index 00000000000..50d90cc7401
--- /dev/null
+++ b/src/HotChocolate/Core/src/Abstractions/Execution/IExecutionTaskDefinition.cs
@@ -0,0 +1,7 @@
+namespace HotChocolate.Execution
+{
+ public interface IExecutionTaskDefinition
+ {
+ IExecutionTask Create(IExecutionTaskContext context);
+ }
+}
diff --git a/src/HotChocolate/Core/src/Abstractions/Execution/QueryResultBuilder.cs b/src/HotChocolate/Core/src/Abstractions/Execution/QueryResultBuilder.cs
index bc5105024ab..b0c5a5321f8 100644
--- a/src/HotChocolate/Core/src/Abstractions/Execution/QueryResultBuilder.cs
+++ b/src/HotChocolate/Core/src/Abstractions/Execution/QueryResultBuilder.cs
@@ -144,7 +144,7 @@ public IReadOnlyQueryResult Create()
public static QueryResultBuilder New() => new QueryResultBuilder();
- public static QueryResultBuilder FromResult(IReadOnlyQueryResult result)
+ public static QueryResultBuilder FromResult(IQueryResult result)
{
var builder = new QueryResultBuilder();
builder._data = result.Data;
diff --git a/src/HotChocolate/Core/src/Abstractions/Execution/SubscriptionResult.cs b/src/HotChocolate/Core/src/Abstractions/Execution/SubscriptionResult.cs
index 70ead95e208..985795736e5 100644
--- a/src/HotChocolate/Core/src/Abstractions/Execution/SubscriptionResult.cs
+++ b/src/HotChocolate/Core/src/Abstractions/Execution/SubscriptionResult.cs
@@ -36,6 +36,32 @@ public SubscriptionResult(
_session = session;
}
+ public SubscriptionResult(
+ SubscriptionResult subscriptionResult,
+ IAsyncDisposable? session = null)
+ {
+ _resultStreamFactory = subscriptionResult._resultStreamFactory;
+ _errors = subscriptionResult._errors;
+ _extensions = subscriptionResult._extensions;
+ _contextData = subscriptionResult._contextData;
+ _session = session is null
+ ? (IAsyncDisposable)subscriptionResult
+ : new CombinedDispose(session, subscriptionResult);
+ }
+
+ public SubscriptionResult(
+ SubscriptionResult subscriptionResult,
+ IDisposable? session = null)
+ {
+ _resultStreamFactory = subscriptionResult._resultStreamFactory;
+ _errors = subscriptionResult._errors;
+ _extensions = subscriptionResult._extensions;
+ _contextData = subscriptionResult._contextData;
+ _session = session is null
+ ? (IAsyncDisposable)subscriptionResult
+ : new CombinedDispose(session.Dispose, subscriptionResult);
+ }
+
public IReadOnlyList? Errors => _errors;
public IReadOnlyDictionary? Extensions => _extensions;
@@ -79,5 +105,40 @@ public async ValueTask DisposeAsync()
}
}
+ private class CombinedDispose : IAsyncDisposable
+ {
+ private readonly IAsyncDisposable? _aa;
+ private readonly Action? _ab;
+ private readonly IAsyncDisposable _b;
+ private bool _disposed = false;
+
+ public CombinedDispose(IAsyncDisposable a, IAsyncDisposable b)
+ {
+ _aa = a;
+ _b = b;
+ }
+
+ public CombinedDispose(Action a, IAsyncDisposable b)
+ {
+ _ab = a;
+ _b = b;
+ }
+
+ public async ValueTask DisposeAsync()
+ {
+ if (!_disposed)
+ {
+ if (_aa is not null)
+ {
+ await _aa.DisposeAsync().ConfigureAwait(false);
+ }
+
+ _ab?.Invoke();
+
+ await _b.DisposeAsync().ConfigureAwait(false);
+ _disposed = true;
+ }
+ }
+ }
}
}
diff --git a/src/HotChocolate/Core/src/Execution/DependencyInjection/InternalServiceCollectionExtensions.cs b/src/HotChocolate/Core/src/Execution/DependencyInjection/InternalServiceCollectionExtensions.cs
index 588794007e0..e09c7acad4b 100644
--- a/src/HotChocolate/Core/src/Execution/DependencyInjection/InternalServiceCollectionExtensions.cs
+++ b/src/HotChocolate/Core/src/Execution/DependencyInjection/InternalServiceCollectionExtensions.cs
@@ -9,6 +9,7 @@
using HotChocolate.Language;
using HotChocolate.Utilities;
using System.Linq;
+using HotChocolate.DataLoader;
namespace Microsoft.Extensions.DependencyInjection
{
@@ -97,5 +98,12 @@ internal static IServiceCollection TryAddDefaultBatchDispatcher(
services.TryAddScoped(sp => sp.GetRequiredService());
return services;
}
+
+ internal static IServiceCollection TryAddDefaultDataLoaderRegistry(
+ this IServiceCollection services)
+ {
+ services.TryAddScoped();
+ return services;
+ }
}
}
diff --git a/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorBuilderExtensions.DataLoader.cs b/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorBuilderExtensions.DataLoader.cs
new file mode 100644
index 00000000000..6903d884e49
--- /dev/null
+++ b/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorBuilderExtensions.DataLoader.cs
@@ -0,0 +1,36 @@
+using System;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using GreenDonut;
+using HotChocolate.Execution.Configuration;
+
+namespace Microsoft.Extensions.DependencyInjection
+{
+ public static partial class RequestExecutorBuilderExtensions
+ {
+ public static IRequestExecutorBuilder AddDataLoader(
+ this IRequestExecutorBuilder builder)
+ where T : class, IDataLoader
+ {
+ builder.Services.TryAddScoped();
+ return builder;
+ }
+
+ public static IRequestExecutorBuilder AddDataLoader(
+ this IRequestExecutorBuilder builder)
+ where TService : class, IDataLoader
+ where TImplementation : class, TService
+ {
+ builder.Services.TryAddScoped();
+ return builder;
+ }
+
+ public static IRequestExecutorBuilder AddDataLoader(
+ this IRequestExecutorBuilder builder,
+ Func factory)
+ where T : class, IDataLoader
+ {
+ builder.Services.TryAddScoped(factory);
+ return builder;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorServiceCollectionExtensions.cs b/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorServiceCollectionExtensions.cs
index 72aa3ce0b86..00bc6d582d4 100644
--- a/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorServiceCollectionExtensions.cs
+++ b/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorServiceCollectionExtensions.cs
@@ -32,6 +32,7 @@ public static IServiceCollection AddGraphQLCore(this IServiceCollection services
services.TryAddDefaultCaches();
services.TryAddDefaultDocumentHashProvider();
services.TryAddDefaultBatchDispatcher();
+ services.TryAddDefaultDataLoaderRegistry();
// pools
services.TryAddResultPool();
diff --git a/src/HotChocolate/Core/src/Execution/DependencyInjection/SchemaRequestExecutorBuilderExtensions.Resolvers.cs b/src/HotChocolate/Core/src/Execution/DependencyInjection/SchemaRequestExecutorBuilderExtensions.Resolvers.cs
index 9b90c1b1b34..d1ec117e80f 100644
--- a/src/HotChocolate/Core/src/Execution/DependencyInjection/SchemaRequestExecutorBuilderExtensions.Resolvers.cs
+++ b/src/HotChocolate/Core/src/Execution/DependencyInjection/SchemaRequestExecutorBuilderExtensions.Resolvers.cs
@@ -166,7 +166,7 @@ public static IRequestExecutorBuilder AddResolver(
this IRequestExecutorBuilder builder,
NameString typeName,
NameString fieldName,
- Func> resolver)
+ Func> resolver)
{
if (builder == null)
{
@@ -225,7 +225,7 @@ public static IRequestExecutorBuilder AddResolver(
this IRequestExecutorBuilder builder,
NameString typeName,
NameString fieldName,
- Func> resolver)
+ Func> resolver)
{
if (builder == null)
{
diff --git a/src/HotChocolate/Core/src/Execution/ExecutionContext.Pooling.cs b/src/HotChocolate/Core/src/Execution/ExecutionContext.Pooling.cs
index 5027601af22..67773544c00 100644
--- a/src/HotChocolate/Core/src/Execution/ExecutionContext.Pooling.cs
+++ b/src/HotChocolate/Core/src/Execution/ExecutionContext.Pooling.cs
@@ -8,12 +8,16 @@ namespace HotChocolate.Execution
internal partial class ExecutionContext
: IExecutionContext
{
+ private readonly IExecutionTaskContext _taskContext;
private readonly TaskBacklog _taskBacklog;
private readonly TaskStatistics _taskStatistics;
private CancellationTokenSource _completed = default!;
- public ExecutionContext(ObjectPool resolverTaskPool)
+ public ExecutionContext(
+ IExecutionTaskContext taskContext,
+ ObjectPool resolverTaskPool)
{
+ _taskContext = taskContext;
_taskStatistics = new TaskStatistics();
_taskBacklog = new TaskBacklog(_taskStatistics, resolverTaskPool);
TaskPool = resolverTaskPool;
@@ -21,7 +25,9 @@ public ExecutionContext(ObjectPool resolverTaskPool)
TaskStats.AllTasksCompleted += OnCompleted;
}
- public void Initialize(IBatchDispatcher batchDispatcher, CancellationToken requestAborted)
+ public void Initialize(
+ IBatchDispatcher batchDispatcher,
+ CancellationToken requestAborted)
{
_completed = new CancellationTokenSource();
requestAborted.Register(TryComplete);
diff --git a/src/HotChocolate/Core/src/Execution/ExecutionContext.cs b/src/HotChocolate/Core/src/Execution/ExecutionContext.cs
index 70d2072bf51..0984fc079e2 100644
--- a/src/HotChocolate/Core/src/Execution/ExecutionContext.cs
+++ b/src/HotChocolate/Core/src/Execution/ExecutionContext.cs
@@ -24,7 +24,12 @@ private void TryDispatchBatches()
{
if (TaskBacklog.IsEmpty && BatchDispatcher.HasTasks)
{
- BatchDispatcher.Dispatch();
+ BatchDispatcher.Dispatch(Register);
+ }
+
+ void Register(IExecutionTaskDefinition taskDefinition)
+ {
+ TaskBacklog.Register(taskDefinition.Create(_taskContext));
}
}
@@ -37,7 +42,7 @@ private void TaskStatisticsEventHandler(
TryDispatchBatches();
private void OnCompleted(
- object? source,
+ object? source,
EventArgs args) =>
_taskBacklog.Complete();
}
diff --git a/src/HotChocolate/Core/src/Execution/Instrumentation/AggregateDiagnosticEvents.cs b/src/HotChocolate/Core/src/Execution/Instrumentation/AggregateDiagnosticEvents.cs
index 85e61b8a583..569f44ef2d0 100644
--- a/src/HotChocolate/Core/src/Execution/Instrumentation/AggregateDiagnosticEvents.cs
+++ b/src/HotChocolate/Core/src/Execution/Instrumentation/AggregateDiagnosticEvents.cs
@@ -102,6 +102,31 @@ public void ResolverError(IMiddlewareContext context, IError error)
}
}
+ public IActivityScope RunTask(IExecutionTask task)
+ {
+ if (_listeners.Length == 0)
+ {
+ return _empty;
+ }
+
+ var scopes = new IActivityScope[_resolverListener.Length];
+
+ for (int i = 0; i < _resolverListener.Length; i++)
+ {
+ scopes[i] = _resolverListener[i].RunTask(task);
+ }
+
+ return new AggregateActivityScope(scopes);
+ }
+
+ public void TaskError(IExecutionTask task, IError error)
+ {
+ for (int i = 0; i < _listeners.Length; i++)
+ {
+ _listeners[i].TaskError(task, error);
+ }
+ }
+
public void AddedDocumentToCache(IRequestContext context)
{
for (int i = 0; i < _listeners.Length; i++)
diff --git a/src/HotChocolate/Core/src/Execution/Instrumentation/DiagnosticEventListener.cs b/src/HotChocolate/Core/src/Execution/Instrumentation/DiagnosticEventListener.cs
index 6cf9d8cb1d4..c6f8ebf1b3f 100644
--- a/src/HotChocolate/Core/src/Execution/Instrumentation/DiagnosticEventListener.cs
+++ b/src/HotChocolate/Core/src/Execution/Instrumentation/DiagnosticEventListener.cs
@@ -38,6 +38,12 @@ public virtual void ResolverError(IMiddlewareContext context, IError error)
{
}
+ public virtual IActivityScope RunTask(IExecutionTask task) => EmptyScope;
+
+ public virtual void TaskError(IExecutionTask task, IError error)
+ {
+ }
+
public virtual void AddedDocumentToCache(IRequestContext context)
{
}
diff --git a/src/HotChocolate/Core/src/Execution/Instrumentation/IDiagnosticEvents.cs b/src/HotChocolate/Core/src/Execution/Instrumentation/IDiagnosticEvents.cs
index 48833c63ccc..8c8c3c7b642 100644
--- a/src/HotChocolate/Core/src/Execution/Instrumentation/IDiagnosticEvents.cs
+++ b/src/HotChocolate/Core/src/Execution/Instrumentation/IDiagnosticEvents.cs
@@ -22,6 +22,10 @@ public interface IDiagnosticEvents
void ResolverError(IMiddlewareContext context, IError error);
+ IActivityScope RunTask(IExecutionTask task);
+
+ void TaskError(IExecutionTask task, IError error);
+
void AddedDocumentToCache(IRequestContext context);
void RetrievedDocumentFromCache(IRequestContext context);
diff --git a/src/HotChocolate/Core/src/Execution/Instrumentation/NoOpDiagnosticEvents.cs b/src/HotChocolate/Core/src/Execution/Instrumentation/NoOpDiagnosticEvents.cs
index 87ec540c8ea..e19fae03328 100644
--- a/src/HotChocolate/Core/src/Execution/Instrumentation/NoOpDiagnosticEvents.cs
+++ b/src/HotChocolate/Core/src/Execution/Instrumentation/NoOpDiagnosticEvents.cs
@@ -40,6 +40,11 @@ public void ResolverError(
IError error)
{ }
+ public IActivityScope RunTask(IExecutionTask task) => this;
+
+ public void TaskError(IExecutionTask task, IError error)
+ { }
+
public void AddedDocumentToCache(
IRequestContext context)
{ }
diff --git a/src/HotChocolate/Core/src/Execution/OperationContext.IExecutionTaskContext.cs b/src/HotChocolate/Core/src/Execution/OperationContext.IExecutionTaskContext.cs
new file mode 100644
index 00000000000..97d90778139
--- /dev/null
+++ b/src/HotChocolate/Core/src/Execution/OperationContext.IExecutionTaskContext.cs
@@ -0,0 +1,29 @@
+using System;
+
+namespace HotChocolate.Execution
+{
+ internal sealed partial class OperationContext : IExecutionTaskContext
+ {
+ void IExecutionTaskContext.ReportError(IExecutionTask task, IError error) =>
+ ReportError(task, error);
+
+ void IExecutionTaskContext.ReportError(IExecutionTask task, Exception exception) =>
+ ReportError(task, ErrorHandler.CreateUnexpectedError(exception).Build());
+
+ private void ReportError(IExecutionTask task, IError error)
+ {
+ error = ErrorHandler.Handle(error);
+ Result.AddError(error);
+ DiagnosticEvents.TaskError(task, error);
+ }
+
+ void IExecutionTaskContext.Started() =>
+ Execution.TaskStats.TaskStarted();
+
+ void IExecutionTaskContext.Completed() =>
+ Execution.TaskStats.TaskCompleted();
+
+ IDisposable IExecutionTaskContext.Track(IExecutionTask task) =>
+ DiagnosticEvents.RunTask(task);
+ }
+}
\ No newline at end of file
diff --git a/src/HotChocolate/Core/src/Execution/OperationContext.Pooling.cs b/src/HotChocolate/Core/src/Execution/OperationContext.Pooling.cs
index cc2cb579c9f..6d47cf50ee2 100644
--- a/src/HotChocolate/Core/src/Execution/OperationContext.Pooling.cs
+++ b/src/HotChocolate/Core/src/Execution/OperationContext.Pooling.cs
@@ -15,7 +15,7 @@ public OperationContext(
ObjectPool resolverTaskPool,
ResultPool resultPool)
{
- _executionContext = new ExecutionContext(resolverTaskPool);
+ _executionContext = new ExecutionContext(this, resolverTaskPool);
_resultHelper = new ResultHelper(resultPool);
}
@@ -28,7 +28,9 @@ public void Initialize(
IVariableValueCollection variables)
{
_requestContext = requestContext;
- _executionContext.Initialize(batchDispatcher, requestContext.RequestAborted);
+ _executionContext.Initialize(
+ batchDispatcher,
+ requestContext.RequestAborted);
Operation = operation;
RootValue = rootValue;
Variables = variables;
diff --git a/src/HotChocolate/Core/src/Execution/RequestExecutor.cs b/src/HotChocolate/Core/src/Execution/RequestExecutor.cs
index 4d9df4b2965..6b8bec0437b 100644
--- a/src/HotChocolate/Core/src/Execution/RequestExecutor.cs
+++ b/src/HotChocolate/Core/src/Execution/RequestExecutor.cs
@@ -6,6 +6,7 @@
using HotChocolate.Execution.Instrumentation;
using HotChocolate.Execution.Utilities;
using HotChocolate.Utilities;
+using Microsoft.Extensions.DependencyInjection;
namespace HotChocolate.Execution
{
@@ -22,7 +23,7 @@ internal sealed class RequestExecutor : IRequestExecutor
public RequestExecutor(
ISchema schema,
IServiceProvider applicationServices,
- IServiceProvider schemaServices,
+ IServiceProvider executorServices,
IErrorHandler errorHandler,
ITypeConverter converter,
IActivator activator,
@@ -33,8 +34,8 @@ public RequestExecutor(
throw new ArgumentNullException(nameof(schema));
_services = applicationServices ??
throw new ArgumentNullException(nameof(applicationServices));
- Services = schemaServices ??
- throw new ArgumentNullException(nameof(schemaServices));
+ Services = executorServices ??
+ throw new ArgumentNullException(nameof(executorServices));
_errorHandler = errorHandler ??
throw new ArgumentNullException(nameof(errorHandler));
_converter = converter ??
@@ -61,26 +62,42 @@ public async Task ExecuteAsync(
throw new ArgumentNullException(nameof(request));
}
- var context = new RequestContext(
- Schema,
- request.Services ?? _services,
- _errorHandler,
- _converter,
- _activator,
- _diagnosticEvents,
- request)
+ IServiceScope? scope = request.Services is null ? _services.CreateScope() : null;
+ IServiceProvider services = scope is null ? request.Services! : scope.ServiceProvider;
+
+ try
{
- RequestAborted = cancellationToken
- };
+ var context = new RequestContext(
+ Schema,
+ services,
+ _errorHandler,
+ _converter,
+ _activator,
+ _diagnosticEvents,
+ request)
+ {
+ RequestAborted = cancellationToken
+ };
+
+ await _requestDelegate(context).ConfigureAwait(false);
+
+ if (context.Result is null)
+ {
+ throw new InvalidOperationException();
+ }
- await _requestDelegate(context).ConfigureAwait(false);
+ if(scope is not null && context.Result is SubscriptionResult result)
+ {
+ context.Result = new SubscriptionResult(result, scope);
+ scope = null;
+ }
- if (context.Result is null)
+ return context.Result;
+ }
+ finally
{
- throw new InvalidOperationException();
+ scope?.Dispose();
}
-
- return context.Result;
}
public Task ExecuteBatchAsync(
diff --git a/src/HotChocolate/Core/src/Execution/Utilities/ITaskBacklog.cs b/src/HotChocolate/Core/src/Execution/Utilities/ITaskBacklog.cs
index 12f197de592..aa4e3f3403e 100644
--- a/src/HotChocolate/Core/src/Execution/Utilities/ITaskBacklog.cs
+++ b/src/HotChocolate/Core/src/Execution/Utilities/ITaskBacklog.cs
@@ -5,7 +5,7 @@
namespace HotChocolate.Execution.Utilities
{
///
- /// The task backlog of the execution engine stores
+ /// The task backlog of the execution engine stores
/// without any guaranteed order.
///
internal interface ITaskBacklog
@@ -23,7 +23,7 @@ internal interface ITaskBacklog
/// The task is not null when the method returnstrue
///
/// Return true if there was a task on the backlog.
- bool TryTake([NotNullWhen(true)] out ITask? task);
+ bool TryTake([NotNullWhen(true)] out IExecutionTask? task);
///
/// Waits for either the to raise or
@@ -39,5 +39,7 @@ internal interface ITaskBacklog
/// Registers work with the task backlog.
///
void Register(ResolverTaskDefinition taskDefinition);
+
+ void Register(IExecutionTask task);
}
}
diff --git a/src/HotChocolate/Core/src/Execution/Utilities/NoopBatchDispatcher.cs b/src/HotChocolate/Core/src/Execution/Utilities/NoopBatchDispatcher.cs
index fc3447c9297..723f092ea10 100644
--- a/src/HotChocolate/Core/src/Execution/Utilities/NoopBatchDispatcher.cs
+++ b/src/HotChocolate/Core/src/Execution/Utilities/NoopBatchDispatcher.cs
@@ -10,7 +10,7 @@ internal class NoopBatchDispatcher
public event EventHandler? TaskEnqueued;
- public void Dispatch()
+ public void Dispatch(Action enqueue)
{
}
diff --git a/src/HotChocolate/Core/src/Execution/Utilities/ResolverExecutionHelper.cs b/src/HotChocolate/Core/src/Execution/Utilities/ResolverExecutionHelper.cs
index 2cf7e751f27..98df5078479 100644
--- a/src/HotChocolate/Core/src/Execution/Utilities/ResolverExecutionHelper.cs
+++ b/src/HotChocolate/Core/src/Execution/Utilities/ResolverExecutionHelper.cs
@@ -34,7 +34,7 @@ private static async Task ExecuteResolvers(
!executionContext.IsCompleted)
{
while (!cancellationToken.IsCancellationRequested &&
- executionContext.TaskBacklog.TryTake(out ITask? task))
+ executionContext.TaskBacklog.TryTake(out IExecutionTask? task))
{
task.BeginExecute();
}
diff --git a/src/HotChocolate/Core/src/Execution/Utilities/ResolverTask.cs b/src/HotChocolate/Core/src/Execution/Utilities/ResolverTask.cs
index 946ecfed26b..e6c85c5a094 100644
--- a/src/HotChocolate/Core/src/Execution/Utilities/ResolverTask.cs
+++ b/src/HotChocolate/Core/src/Execution/Utilities/ResolverTask.cs
@@ -7,7 +7,7 @@
namespace HotChocolate.Execution.Utilities
{
- internal sealed partial class ResolverTask : ITask
+ internal sealed partial class ResolverTask : IExecutionTask
{
private readonly MiddlewareContext _context = new MiddlewareContext();
private ValueTask _task;
diff --git a/src/HotChocolate/Core/src/Execution/Utilities/TaskBacklog.cs b/src/HotChocolate/Core/src/Execution/Utilities/TaskBacklog.cs
index a710816a399..788a41fb7e4 100644
--- a/src/HotChocolate/Core/src/Execution/Utilities/TaskBacklog.cs
+++ b/src/HotChocolate/Core/src/Execution/Utilities/TaskBacklog.cs
@@ -11,8 +11,8 @@ internal class TaskBacklog : ITaskBacklog
{
private readonly ObjectPool _resolverTaskPool;
private readonly ITaskStatistics _stats;
- private UnsortedChannel _channel =
- new UnsortedChannel(true);
+ private UnsortedChannel _channel =
+ new UnsortedChannel(true);
internal TaskBacklog(
ITaskStatistics stats,
@@ -26,7 +26,7 @@ internal TaskBacklog(
public bool IsEmpty => _channel.IsEmpty;
///
- public bool TryTake([NotNullWhen(true)] out ITask? task) =>
+ public bool TryTake([NotNullWhen(true)] out IExecutionTask? task) =>
_channel.Reader.TryRead(out task);
///
@@ -47,15 +47,20 @@ public void Register(ResolverTaskDefinition taskDefinition)
taskDefinition.Path,
taskDefinition.ScopedContextData);
+ Register(resolverTask);
+ }
+
+ public void Register(IExecutionTask task)
+ {
_stats.TaskCreated();
- _channel.Writer.TryWrite(resolverTask);
+ _channel.Writer.TryWrite(task);
}
public void Complete() => _channel.Writer.Complete();
public void Reset()
{
- _channel = new UnsortedChannel(true);
+ _channel = new UnsortedChannel(true);
}
}
}
diff --git a/src/HotChocolate/Core/src/Fetching/AutoBatchScheduler.cs b/src/HotChocolate/Core/src/Fetching/AutoBatchScheduler.cs
index 629fac77b76..a76e4774f64 100644
--- a/src/HotChocolate/Core/src/Fetching/AutoBatchScheduler.cs
+++ b/src/HotChocolate/Core/src/Fetching/AutoBatchScheduler.cs
@@ -1,4 +1,5 @@
using System;
+using System.Threading.Tasks;
using GreenDonut;
namespace HotChocolate.Fetching
@@ -7,6 +8,8 @@ public class AutoBatchScheduler
: IBatchScheduler
, IAutoBatchDispatcher
{
- public void Schedule(Action dispatch) => dispatch();
+ public void Schedule(Func dispatch) => dispatch();
+
+ public static AutoBatchScheduler Default { get; } = new AutoBatchScheduler();
}
}
diff --git a/src/HotChocolate/Core/src/Fetching/BatchScheduler.cs b/src/HotChocolate/Core/src/Fetching/BatchScheduler.cs
index bf3423bcfec..0356b2aed3e 100644
--- a/src/HotChocolate/Core/src/Fetching/BatchScheduler.cs
+++ b/src/HotChocolate/Core/src/Fetching/BatchScheduler.cs
@@ -1,9 +1,9 @@
-using System.Security.Cryptography;
using System;
using System.Collections.Concurrent;
-using System.Threading;
+using System.Collections.Generic;
using System.Threading.Tasks;
using GreenDonut;
+using HotChocolate.Execution;
namespace HotChocolate.Fetching
{
@@ -11,24 +11,100 @@ public class BatchScheduler
: IBatchScheduler
, IBatchDispatcher
{
- private readonly ConcurrentQueue _queue = new ConcurrentQueue();
+ private readonly ConcurrentQueue> _queue =
+ new ConcurrentQueue>();
public bool HasTasks => _queue.Count > 0;
public event EventHandler? TaskEnqueued;
- public void Dispatch()
+ public void Dispatch(Action enqueue)
{
- while (_queue.TryDequeue(out Action? dispatch))
+ lock (_queue)
{
- dispatch();
+ if (_queue.Count > 0)
+ {
+ var tasks = new List>();
+
+ while (_queue.TryDequeue(out Func? dispatch))
+ {
+ tasks.Add(dispatch);
+ }
+
+ enqueue(new BatchExecutionTaskDefinition(tasks));
+ }
}
}
- public void Schedule(Action dispatch)
+ public void Schedule(Func dispatch)
{
_queue.Enqueue(dispatch);
TaskEnqueued?.Invoke(this, EventArgs.Empty);
}
+
+ private class BatchExecutionTaskDefinition
+ : IExecutionTaskDefinition
+ {
+ private readonly IReadOnlyList> _tasks;
+
+ public BatchExecutionTaskDefinition(IReadOnlyList> tasks)
+ {
+ _tasks = tasks;
+ }
+
+ public IExecutionTask Create(IExecutionTaskContext context)
+ {
+ return new BatchExecutionTask(context, _tasks);
+ }
+ }
+
+ private class BatchExecutionTask
+ : IExecutionTask
+ {
+ private readonly IExecutionTaskContext _context;
+ private readonly IReadOnlyList> _tasks;
+ private ValueTask _task;
+
+ public BatchExecutionTask(
+ IExecutionTaskContext context,
+ IReadOnlyList> tasks)
+ {
+ _context = context;
+ _tasks = tasks;
+ }
+
+ public void BeginExecute()
+ {
+ _context.Started();
+ _task = ExecuteAsync();
+ }
+
+ public bool IsCompleted => _task.IsCompleted;
+
+ private async ValueTask ExecuteAsync()
+ {
+ try
+ {
+ using (_context.Track(this))
+ {
+ foreach (Func task in _tasks)
+ {
+ try
+ {
+ await task.Invoke().ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ _context.ReportError(this, ex);
+ }
+ }
+ }
+ }
+ finally
+ {
+ _context.Completed();
+ }
+ }
+ }
}
}
diff --git a/src/HotChocolate/Core/src/Fetching/IBatchDispatcher.cs b/src/HotChocolate/Core/src/Fetching/IBatchDispatcher.cs
index 5ac134c13b6..14676723432 100644
--- a/src/HotChocolate/Core/src/Fetching/IBatchDispatcher.cs
+++ b/src/HotChocolate/Core/src/Fetching/IBatchDispatcher.cs
@@ -1,13 +1,14 @@
using System;
+using HotChocolate.Execution;
namespace HotChocolate.Fetching
{
public interface IBatchDispatcher
{
- bool HasTasks { get; }
-
event EventHandler? TaskEnqueued;
- void Dispatch();
+ bool HasTasks { get; }
+
+ void Dispatch(Action enqueue);
}
}
diff --git a/src/HotChocolate/Core/src/Types/DataLoader/BatchDataLoader.cs b/src/HotChocolate/Core/src/Types/DataLoader/BatchDataLoader.cs
index 400f77e6195..1a708fce515 100644
--- a/src/HotChocolate/Core/src/Types/DataLoader/BatchDataLoader.cs
+++ b/src/HotChocolate/Core/src/Types/DataLoader/BatchDataLoader.cs
@@ -1,19 +1,23 @@
-using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using GreenDonut;
+#nullable enable
+
namespace HotChocolate.DataLoader
{
public abstract class BatchDataLoader
: DataLoaderBase
+ where TKey : notnull
{
- protected BatchDataLoader(IBatchScheduler batchScheduler)
- : base(batchScheduler)
+ protected BatchDataLoader(
+ IBatchScheduler batchScheduler,
+ DataLoaderOptions? options = null)
+ : base(batchScheduler, options)
{ }
- protected sealed override async Task>> FetchAsync(
+ protected sealed override async ValueTask>> FetchAsync(
IReadOnlyList keys,
CancellationToken cancellationToken)
{
diff --git a/src/HotChocolate/Core/src/Types/DataLoader/CacheDataLoader.cs b/src/HotChocolate/Core/src/Types/DataLoader/CacheDataLoader.cs
index 7fcbfb0b83a..62728012e3d 100644
--- a/src/HotChocolate/Core/src/Types/DataLoader/CacheDataLoader.cs
+++ b/src/HotChocolate/Core/src/Types/DataLoader/CacheDataLoader.cs
@@ -5,20 +5,21 @@
using GreenDonut;
using HotChocolate.Fetching;
+#nullable enable
+
namespace HotChocolate.DataLoader
{
public abstract class CacheDataLoader
: DataLoaderBase
+ where TKey : notnull
{
- protected CacheDataLoader(IAutoBatchDispatcher batchScheduler, FetchCache fetch)
- : base(batchScheduler)
- { }
-
- protected CacheDataLoader(IAutoBatchDispatcher batchScheduler, int cacheSize)
- : base(batchScheduler, new DataLoaderOptions { CacheSize = cacheSize })
+ protected CacheDataLoader(int cacheSize)
+ : base(
+ AutoBatchScheduler.Default,
+ new DataLoaderOptions { CacheSize = cacheSize, MaxBatchSize = 1 })
{ }
- protected sealed override async Task>> FetchAsync(
+ protected sealed override async ValueTask>> FetchAsync(
IReadOnlyList keys,
CancellationToken cancellationToken)
{
@@ -40,6 +41,8 @@ protected sealed override async Task>> FetchAsync(
return items;
}
- protected abstract Task LoadSingleAsync(TKey key, CancellationToken cancellationToken);
+ protected abstract Task LoadSingleAsync(
+ TKey key,
+ CancellationToken cancellationToken);
}
}
diff --git a/src/HotChocolate/Core/src/Types/DataLoader/DataLoaderBatchOperation.cs b/src/HotChocolate/Core/src/Types/DataLoader/DataLoaderBatchOperation.cs
deleted file mode 100644
index b812636f5fd..00000000000
--- a/src/HotChocolate/Core/src/Types/DataLoader/DataLoaderBatchOperation.cs
+++ /dev/null
@@ -1,92 +0,0 @@
-using System;
-using System.Collections.Immutable;
-using System.Threading;
-using System.Threading.Tasks;
-using GreenDonut;
-using HotChocolate.Execution;
-
-namespace HotChocolate.DataLoader
-{
- public sealed class DataLoaderBatchOperation
- : IBatchOperation
- , IObserver
- {
- private readonly object _sync = new object();
-
- private ImmutableHashSet _touched =
- ImmutableHashSet.Empty;
-
- public int BufferSize => _touched.Count;
-
- public event EventHandler BufferedRequests;
-
- public async Task InvokeAsync(CancellationToken cancellationToken)
- {
- /*
- foreach (IDataLoader dataLoader in GetTouchedDataLoaders())
- {
- if (dataLoader.BufferedRequests > 0)
- {
- await dataLoader.DispatchAsync(cancellationToken)
- .ConfigureAwait(false);
- }
- }
- */
- throw new NotImplementedException();
- }
-
- private void RequestBuffered(IDataLoader sender, EventArgs eventArgs)
- {
- if (!_touched.Contains(sender))
- {
- lock (_sync)
- {
- _touched = _touched.Add(sender);
- }
- }
-
- BufferedRequests(this, EventArgs.Empty);
- }
-
- private ImmutableHashSet GetTouchedDataLoaders()
- {
- lock (_sync)
- {
- ImmutableHashSet touched = _touched;
- _touched = ImmutableHashSet.Empty;
- return touched;
- }
- }
-
- public void OnNext(IDataLoader value)
- {
- if (value == null)
- {
- throw new ArgumentNullException(nameof(value));
- }
-
- if (!_touched.Contains(value))
- {
- lock (_sync)
- {
- if (!_touched.Contains(value))
- {
- _touched = _touched.Add(value);
- // value.RequestBuffered += RequestBuffered;
- throw new NotImplementedException();
- }
- }
- }
-
- BufferedRequests(this, EventArgs.Empty);
- }
-
- public void OnCompleted()
- {
- }
-
- public void OnError(Exception error)
- {
- }
- }
-}
diff --git a/src/HotChocolate/Core/src/Types/DataLoader/DataLoaderDefaults.cs b/src/HotChocolate/Core/src/Types/DataLoader/DataLoaderDefaults.cs
index 9b68177a92d..09d79b217fa 100644
--- a/src/HotChocolate/Core/src/Types/DataLoader/DataLoaderDefaults.cs
+++ b/src/HotChocolate/Core/src/Types/DataLoader/DataLoaderDefaults.cs
@@ -3,7 +3,5 @@
internal static class DataLoaderDefaults
{
public const int CacheSize = 100;
- public const int MinCacheSize = 1;
- public const int MaxBatchSize = 0;
}
}
diff --git a/src/HotChocolate/Core/src/Types/DataLoader/DataLoaderRegistry.cs b/src/HotChocolate/Core/src/Types/DataLoader/DataLoaderRegistry.cs
deleted file mode 100644
index 9417c4d690f..00000000000
--- a/src/HotChocolate/Core/src/Types/DataLoader/DataLoaderRegistry.cs
+++ /dev/null
@@ -1,143 +0,0 @@
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Immutable;
-using GreenDonut;
-using HotChocolate.Properties;
-
-namespace HotChocolate.DataLoader
-{
- public sealed class DataLoaderRegistry
- : IDataLoaderRegistry
- , IDisposable
- {
- private readonly object _sync = new object();
- private readonly ConcurrentDictionary> _factories =
- new ConcurrentDictionary>();
- private readonly ConcurrentDictionary _instances =
- new ConcurrentDictionary();
- private ImmutableHashSet> _observers =
- ImmutableHashSet>.Empty;
- private readonly IServiceProvider _services;
-
- public DataLoaderRegistry(IServiceProvider services)
- {
- _services = services
- ?? throw new ArgumentNullException(nameof(services));
- }
-
- public IDisposable Subscribe(IObserver observer)
- {
- if (observer == null)
- {
- throw new ArgumentNullException(nameof(observer));
- }
-
- lock (_sync)
- {
- _observers = _observers.Add(observer);
- }
-
- return new ObserverSession(() =>
- {
- lock (_sync)
- {
- _observers = _observers.Remove(observer);
- }
- });
- }
-
- public bool Register(string key, Func factory)
- where T : IDataLoader
- {
- if (string.IsNullOrEmpty(key))
- {
- throw new ArgumentException(
- TypeResources.DataLoaderRegistry_KeyNullOrEmpty,
- nameof(key));
- }
-
- if (factory == null)
- {
- throw new ArgumentNullException(nameof(factory));
- }
-
- return _factories.TryAdd(key, () => factory(_services));
- }
-
- public bool TryGet(string key, out T dataLoader)
- where T : IDataLoader
- {
- if (string.IsNullOrEmpty(key))
- {
- throw new ArgumentException(
- TypeResources.DataLoaderRegistry_KeyNullOrEmpty,
- nameof(key));
- }
-
- if (!_instances.TryGetValue(key, out IDataLoader loader)
- && _factories.TryGetValue(key, out Func factory))
- {
- lock (factory)
- {
- if (!_instances.TryGetValue(key, out loader))
- {
- loader = _instances.GetOrAdd(key, k => factory());
- NotifyObservers(loader);
- }
- }
- }
-
- if (loader is T l)
- {
- dataLoader = l;
- return true;
- }
-
- dataLoader = default(T);
- return false;
- }
-
- private void NotifyObservers(IDataLoader dataLoader)
- {
- foreach (IObserver observer in _observers)
- {
- observer.OnNext(dataLoader);
- }
- }
-
- public void Dispose()
- {
- foreach (IObserver observer in _observers)
- {
- observer.OnCompleted();
- }
-
- foreach (IDataLoader dataLoader in _instances.Values)
- {
- if (dataLoader is IDisposable d)
- {
- d.Dispose();
- }
- }
-
- _observers = _observers.Clear();
- _instances.Clear();
- }
-
- private class ObserverSession
- : IDisposable
- {
- private readonly Action _unregister;
-
- public ObserverSession(Action unregister)
- {
- _unregister = unregister;
- }
-
- public void Dispose()
- {
- _unregister();
- }
- }
- }
-}
diff --git a/src/HotChocolate/Core/src/Types/DataLoader/DataLoaderRegistryExtensions.cs b/src/HotChocolate/Core/src/Types/DataLoader/DataLoaderRegistryExtensions.cs
deleted file mode 100644
index d75bb19a580..00000000000
--- a/src/HotChocolate/Core/src/Types/DataLoader/DataLoaderRegistryExtensions.cs
+++ /dev/null
@@ -1,234 +0,0 @@
-using System;
-using GreenDonut;
-using HotChocolate.Properties;
-using HotChocolate.Utilities;
-
-namespace HotChocolate.DataLoader
-{
- public static class DataLoaderRegistryExtensions
- {
- public static bool Register(
- this IDataLoaderRegistry registry,
- string key,
- FetchBatchFactory factory)
- {
- if (string.IsNullOrEmpty(key))
- {
- throw new ArgumentException(
- TypeResources.DataLoaderRegistry_KeyNullOrEmpty,
- nameof(key));
- }
-
- if (factory == null)
- {
- throw new ArgumentNullException(nameof(factory));
- }
-
- throw new NotImplementedException();
-
- /*
- return registry.Register(key, services =>
- new FetchBatchDataLoader(
- factory(services)));
- */
- }
-
- public static bool Register(
- this IDataLoaderRegistry registry,
- string key,
- FetchBatch fetch)
- {
- if (string.IsNullOrEmpty(key))
- {
- throw new ArgumentException(
- TypeResources.DataLoaderRegistry_KeyNullOrEmpty,
- nameof(key));
- }
-
- if (fetch == null)
- {
- throw new ArgumentNullException(nameof(fetch));
- }
-
- throw new NotImplementedException();
-
- /*
- return registry.Register(key, services =>
- new FetchBatchDataLoader(fetch));*/
- }
-
- public static bool Register(
- this IDataLoaderRegistry registry,
- string key,
- FetchGroupeFactory factory)
- {
- if (string.IsNullOrEmpty(key))
- {
- throw new ArgumentException(
- TypeResources.DataLoaderRegistry_KeyNullOrEmpty,
- nameof(key));
- }
-
- if (factory == null)
- {
- throw new ArgumentNullException(nameof(factory));
- }
-
- throw new NotImplementedException();
-
- /*
- return registry.Register(key, services =>
- new FetchGroupedDataLoader(
- factory(services)));*/
- }
-
- public static bool Register(
- this IDataLoaderRegistry registry,
- string key,
- FetchGroup fetch)
- {
- if (string.IsNullOrEmpty(key))
- {
- throw new ArgumentException(
- TypeResources.DataLoaderRegistry_KeyNullOrEmpty,
- nameof(key));
- }
-
- if (fetch == null)
- {
- throw new ArgumentNullException(nameof(fetch));
- }
-
- throw new NotImplementedException();
-
- /*
- return registry.Register(key, services =>
- new FetchGroupedDataLoader(fetch));
- */
- }
-
- public static bool Register(
- this IDataLoaderRegistry registry,
- string key,
- FetchCacheFactory factory)
- {
- if (string.IsNullOrEmpty(key))
- {
- throw new ArgumentException(
- TypeResources.DataLoaderRegistry_KeyNullOrEmpty,
- nameof(key));
- }
-
- if (factory == null)
- {
- throw new ArgumentNullException(nameof(factory));
- }
-
- throw new NotImplementedException();
-
- /*
- return registry.Register(key, services =>
- new FetchSingleDataLoader(
- factory(services)));
- */
- }
-
- public static bool Register(
- this IDataLoaderRegistry registry,
- string key,
- FetchCache fetch)
- {
- if (string.IsNullOrEmpty(key))
- {
- throw new ArgumentException(
- TypeResources.DataLoaderRegistry_KeyNullOrEmpty,
- nameof(key));
- }
-
- if (fetch == null)
- {
- throw new ArgumentNullException(nameof(fetch));
- }
-
- throw new NotImplementedException();
-
- /*
- return registry.Register(key, services =>
- new FetchSingleDataLoader(fetch));
- */
- }
-
- public static bool Register(
- this IDataLoaderRegistry registry,
- string key,
- FetchOnceFactory factory)
- {
- if (string.IsNullOrEmpty(key))
- {
- throw new ArgumentException(
- TypeResources.DataLoaderRegistry_KeyNullOrEmpty,
- nameof(key));
- }
-
- if (factory == null)
- {
- throw new ArgumentNullException(nameof(factory));
- }
-
- throw new NotImplementedException();
-
-/*
- return registry.Register(key, services =>
- {
- FetchOnce fetch = factory(services);
- return new FetchSingleDataLoader(
- k => fetch(), DataLoaderDefaults.MinCacheSize);
- });*/
- }
-
- public static bool Register(
- this IDataLoaderRegistry registry,
- string key,
- FetchOnce fetch)
- {
- if (string.IsNullOrEmpty(key))
- {
- throw new ArgumentException(
- TypeResources.DataLoaderRegistry_KeyNullOrEmpty,
- nameof(key));
- }
-
- if (fetch == null)
- {
- throw new ArgumentNullException(nameof(fetch));
- }
-
- throw new NotImplementedException();
-
- /*
- return registry.Register(key, services =>
- {
- return new FetchSingleDataLoader(
- k => fetch(), DataLoaderDefaults.MinCacheSize);
- });
- */
- }
-
- public static bool Register(
- this IDataLoaderRegistry registry,
- string key)
- where T : class, IDataLoader
- {
- CreateServiceDelegate createInstance = ActivatorHelper.CompileFactory();
- return registry.Register(key, s => createInstance(s));
- }
-
- public static bool Register(
- this IDataLoaderRegistry registry)
- where T : class, IDataLoader
- {
- CreateServiceDelegate createInstance = ActivatorHelper.CompileFactory();
- return registry.Register(typeof(T).FullName, s => createInstance(s));
- }
- }
-}
diff --git a/src/HotChocolate/Core/src/Types/DataLoader/DataLoaderResolverContextExtensions.cs b/src/HotChocolate/Core/src/Types/DataLoader/DataLoaderResolverContextExtensions.cs
index 41f06b86e37..842c2064ebb 100644
--- a/src/HotChocolate/Core/src/Types/DataLoader/DataLoaderResolverContextExtensions.cs
+++ b/src/HotChocolate/Core/src/Types/DataLoader/DataLoaderResolverContextExtensions.cs
@@ -1,66 +1,54 @@
using System;
-using System.Collections.Generic;
+using System.Threading;
using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
using GreenDonut;
using HotChocolate.DataLoader;
using HotChocolate.Properties;
using HotChocolate.Utilities;
+using static HotChocolate.Properties.TypeResources;
+
+#nullable enable
namespace HotChocolate.Resolvers
{
public static class DataLoaderResolverContextExtensions
{
- private static IDataLoader BatchDataLoaderFactory(
- this IResolverContext context,
- string key,
- FetchBatchFactory factory)
- {
- if (TryGetDataLoader(context, key,
- out IDataLoader dataLoader,
- out IDataLoaderRegistry registry))
- {
- return dataLoader;
- }
-
- return GetOrCreate>(
- key, registry, r => r.Register(key, factory));
- }
-
public static IDataLoader BatchDataLoader(
this IResolverContext context,
- string key,
- FetchBatch fetch)
+ FetchBatch fetch,
+ string? key = null)
+ where TKey : notnull
{
- if (context == null)
+ if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
- if (string.IsNullOrEmpty(key))
- {
- throw new ArgumentException(
- TypeResources.DataLoaderRegistry_KeyNullOrEmpty,
- nameof(key));
- }
-
- if (fetch == null)
+ if (fetch is null)
{
throw new ArgumentNullException(nameof(fetch));
}
- return BatchDataLoaderFactory(context, key, services => fetch);
+ IServiceProvider services = context.Services;
+ IDataLoaderRegistry reg = services.GetRequiredService();
+ Func> createDataLoader =
+ () => new FetchBatchDataLoader(
+ services.GetRequiredService(),
+ fetch);
+
+ return key is null
+ ? reg.GetOrRegister(createDataLoader)
+ : reg.GetOrRegister(key, createDataLoader);
}
+ [Obsolete]
public static IDataLoader BatchDataLoader(
this IResolverContext context,
string key,
- FetchBatchCt fetch)
+ FetchBatch fetch)
+ where TKey : notnull
{
- if (context == null)
- {
- throw new ArgumentNullException(nameof(context));
- }
-
if (string.IsNullOrEmpty(key))
{
throw new ArgumentException(
@@ -68,68 +56,44 @@ public static IDataLoader BatchDataLoader(
nameof(key));
}
- if (fetch == null)
- {
- throw new ArgumentNullException(nameof(fetch));
- }
-
- return BatchDataLoader(
- context,
- key,
- keys => fetch(keys, context.RequestAborted));
- }
-
- private static IDataLoader GroupDataLoaderFactory(
- this IResolverContext context,
- string key,
- FetchGroupeFactory factory)
- {
- if (TryGetDataLoader(context, key,
- out IDataLoader dataLoader,
- out IDataLoaderRegistry registry))
- {
- return dataLoader;
- }
-
- return GetOrCreate>(
- key, registry, r => r.Register(key, factory));
+ return BatchDataLoader(context, fetch, key);
}
public static IDataLoader GroupDataLoader(
this IResolverContext context,
- string key,
- FetchGroup fetch)
+ FetchGroup fetch,
+ string? key = null)
+ where TKey : notnull
{
- if (context == null)
+ if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
- if (string.IsNullOrEmpty(key))
- {
- throw new ArgumentException(
- TypeResources.DataLoaderRegistry_KeyNullOrEmpty,
- nameof(key));
- }
-
- if (fetch == null)
+ if (fetch is null)
{
throw new ArgumentNullException(nameof(fetch));
}
- return GroupDataLoaderFactory(context, key, services => fetch);
+ IServiceProvider services = context.Services;
+ IDataLoaderRegistry reg = services.GetRequiredService();
+ Func> createDataLoader =
+ () => new FetchGroupedDataLoader(
+ services.GetRequiredService(),
+ fetch);
+
+ return key is null
+ ? reg.GetOrRegister(createDataLoader)
+ : reg.GetOrRegister(key, createDataLoader);
}
+ [Obsolete]
public static IDataLoader GroupDataLoader(
this IResolverContext context,
string key,
- FetchGroupCt fetch)
+ FetchGroup fetch)
+ where TKey : notnull
{
- if (context == null)
- {
- throw new ArgumentNullException(nameof(context));
- }
-
if (string.IsNullOrEmpty(key))
{
throw new ArgumentException(
@@ -137,68 +101,46 @@ public static IDataLoader GroupDataLoader(
nameof(key));
}
- if (fetch == null)
- {
- throw new ArgumentNullException(nameof(fetch));
- }
-
- return GroupDataLoader(
- context,
- key,
- keys => fetch(keys, context.RequestAborted));
- }
-
- private static IDataLoader CacheDataLoaderFactory(
- this IResolverContext context,
- string key,
- FetchCacheFactory factory)
- {
- if (TryGetDataLoader(context, key,
- out IDataLoader dataLoader,
- out IDataLoaderRegistry registry))
- {
- return dataLoader;
- }
-
- return GetOrCreate>(
- key, registry, r => r.Register(key, factory));
+ return GroupDataLoader(context, fetch, key);
}
public static IDataLoader CacheDataLoader(
this IResolverContext context,
- string key,
- FetchCache fetch)
+ FetchCacheCt fetch,
+ string? key = null,
+ int cacheSize = DataLoaderDefaults.CacheSize)
+ where TKey : notnull
{
- if (context == null)
+ if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
- if (string.IsNullOrEmpty(key))
- {
- throw new ArgumentException(
- TypeResources.DataLoaderRegistry_KeyNullOrEmpty,
- nameof(key));
- }
-
- if (fetch == null)
+ if (fetch is null)
{
throw new ArgumentNullException(nameof(fetch));
}
- return CacheDataLoaderFactory(context, key, services => fetch);
+ IServiceProvider services = context.Services;
+ IDataLoaderRegistry reg = services.GetRequiredService();
+ Func> createDataLoader =
+ () => new FetchCacheDataLoader(
+ fetch,
+ cacheSize);
+
+ return key is null
+ ? reg.GetOrRegister(createDataLoader)
+ : reg.GetOrRegister(key, createDataLoader);
}
+ [Obsolete]
public static IDataLoader CacheDataLoader(
this IResolverContext context,
string key,
- FetchCacheCt fetch)
+ FetchCacheCt fetch,
+ int cacheSize = DataLoaderDefaults.CacheSize)
+ where TKey : notnull
{
- if (context == null)
- {
- throw new ArgumentNullException(nameof(context));
- }
-
if (string.IsNullOrEmpty(key))
{
throw new ArgumentException(
@@ -206,68 +148,38 @@ public static IDataLoader CacheDataLoader(
nameof(key));
}
- if (fetch == null)
- {
- throw new ArgumentNullException(nameof(fetch));
- }
-
- return CacheDataLoader(
- context,
- key,
- keys => fetch(keys, context.RequestAborted));
- }
-
- private static Func> FetchOnceFactory(
- this IResolverContext context,
- string key,
- FetchOnceFactory factory)
- {
- if (!TryGetDataLoader(context, key,
- out IDataLoader dataLoader,
- out IDataLoaderRegistry registry))
- {
- dataLoader = GetOrCreate>(
- key, registry, r => r.Register(key, factory));
- }
-
- return () => dataLoader.LoadAsync("none");
+ return CacheDataLoader(context, fetch, key, cacheSize);
}
public static Task FetchOnceAsync(
this IResolverContext context,
- string key,
- FetchOnce fetch)
+ Func> fetch,
+ string? key = null)
{
- if (context == null)
+ if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
- if (string.IsNullOrEmpty(key))
- {
- throw new ArgumentException(
- TypeResources.DataLoaderRegistry_KeyNullOrEmpty,
- nameof(key));
- }
-
- if (fetch == null)
+ if (fetch is null)
{
throw new ArgumentNullException(nameof(fetch));
}
- return FetchOnceFactory(context, key, services => fetch)();
+ return CacheDataLoader(
+ context,
+ (k, ct) => fetch(ct),
+ key,
+ cacheSize: 1)
+ .LoadAsync("default", context.RequestAborted);
}
+ [Obsolete]
public static Task