-
-
Notifications
You must be signed in to change notification settings - Fork 749
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added DataLoader Scoping Improvements (#6943)
- Loading branch information
1 parent
7d55af3
commit 854d763
Showing
35 changed files
with
598 additions
and
340 deletions.
There are no files selected for viewing
14 changes: 14 additions & 0 deletions
14
src/GreenDonut/src/Core/DependencyInjection/DataLoaderFactory.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
using System; | ||
|
||
namespace GreenDonut.DependencyInjection; | ||
|
||
/// <summary> | ||
/// Represents a factory that creates a DataLoader instance. | ||
/// </summary> | ||
public delegate IDataLoader DataLoaderFactory(IServiceProvider serviceProvider); | ||
|
||
/// <summary> | ||
/// Represents a factory that creates a DataLoader instance. | ||
/// </summary> | ||
public delegate T DataLoaderFactory<out T>(IServiceProvider serviceProvider) | ||
where T : IDataLoader; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
136 changes: 136 additions & 0 deletions
136
src/GreenDonut/src/Core/DependencyInjection/DataLoaderServiceCollectionExtension.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
using System; | ||
using System.Collections.Concurrent; | ||
#if NET8_0_OR_GREATER | ||
using System.Collections.Frozen; | ||
#endif | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using GreenDonut; | ||
using GreenDonut.DependencyInjection; | ||
using Microsoft.Extensions.DependencyInjection.Extensions; | ||
using Microsoft.Extensions.ObjectPool; | ||
|
||
namespace Microsoft.Extensions.DependencyInjection; | ||
|
||
public static class DataLoaderServiceCollectionExtension | ||
{ | ||
public static IServiceCollection AddDataLoader<T>( | ||
this IServiceCollection services) | ||
where T : class, IDataLoader | ||
{ | ||
services.TryAddDataLoaderCore(); | ||
services.AddSingleton(new DataLoaderRegistration(typeof(T))); | ||
services.TryAddScoped<T>(sp => sp.GetDataLoader<T>()); | ||
return services; | ||
} | ||
|
||
public static IServiceCollection AddDataLoader<TService, TImplementation>( | ||
this IServiceCollection services) | ||
where TService : class, IDataLoader | ||
where TImplementation : class, TService | ||
{ | ||
services.TryAddDataLoaderCore(); | ||
services.AddSingleton(new DataLoaderRegistration(typeof(TService), typeof(TImplementation))); | ||
services.TryAddScoped<TImplementation>(sp => sp.GetDataLoader<TImplementation>()); | ||
services.TryAddScoped<TService>(sp => sp.GetDataLoader<TService>()); | ||
return services; | ||
} | ||
|
||
public static IServiceCollection AddDataLoader<T>( | ||
this IServiceCollection services, | ||
Func<IServiceProvider, T> factory) | ||
where T : class, IDataLoader | ||
{ | ||
services.TryAddDataLoaderCore(); | ||
services.AddSingleton(new DataLoaderRegistration(typeof(T), sp => factory(sp))); | ||
services.TryAddScoped<T>(sp => sp.GetDataLoader<T>()); | ||
return services; | ||
} | ||
|
||
public static IServiceCollection TryAddDataLoaderCore( | ||
this IServiceCollection services) | ||
{ | ||
services.TryAddScoped<IDataLoaderScope, DefaultDataLoaderScope>(); | ||
services.TryAddScoped<IBatchScheduler, AutoBatchScheduler>(); | ||
|
||
services.TryAddSingleton(sp => TaskCachePool.Create(sp.GetRequiredService<ObjectPoolProvider>())); | ||
services.TryAddScoped(sp => new TaskCacheOwner(sp.GetRequiredService<ObjectPool<TaskCache>>())); | ||
|
||
services.TryAddSingleton<IDataLoaderDiagnosticEvents>( | ||
sp => | ||
{ | ||
var listeners = sp.GetServices<IDataLoaderDiagnosticEventListener>().ToArray(); | ||
|
||
return listeners.Length switch | ||
{ | ||
0 => new DataLoaderDiagnosticEventListener(), | ||
1 => listeners[0], | ||
_ => new AggregateDataLoaderDiagnosticEventListener(listeners), | ||
}; | ||
}); | ||
|
||
services.TryAddScoped( | ||
sp => | ||
{ | ||
var cacheOwner = sp.GetRequiredService<TaskCacheOwner>(); | ||
|
||
return new DataLoaderOptions | ||
{ | ||
Cache = cacheOwner.Cache, | ||
CancellationToken = cacheOwner.CancellationToken, | ||
DiagnosticEvents = sp.GetService<IDataLoaderDiagnosticEvents>(), | ||
MaxBatchSize = 1024, | ||
}; | ||
}); | ||
|
||
return services; | ||
} | ||
} | ||
|
||
file static class DataLoaderServiceProviderExtensions | ||
{ | ||
public static T GetDataLoader<T>(this IServiceProvider services) where T : IDataLoader | ||
=> services.GetRequiredService<IDataLoaderScope>().GetDataLoader<T>(); | ||
} | ||
|
||
file sealed class DefaultDataLoaderScope( | ||
IServiceProvider serviceProvider, | ||
#if NET8_0_OR_GREATER | ||
FrozenDictionary<Type, DataLoaderRegistration> registrations) | ||
#else | ||
Dictionary<Type, DataLoaderRegistration> registrations) | ||
#endif | ||
: IDataLoaderScope | ||
{ | ||
private readonly ConcurrentDictionary<string, IDataLoader> _dataLoaders = new(); | ||
|
||
|
||
public T GetDataLoader<T>(DataLoaderFactory<T> createDataLoader, string? name = null) where T : IDataLoader | ||
{ | ||
name ??= CreateKey<T>(); | ||
|
||
if (_dataLoaders.GetOrAdd(name, _ => createDataLoader(serviceProvider)) is T dataLoader) | ||
{ | ||
return dataLoader; | ||
} | ||
|
||
throw new InvalidOperationException("A with the same name already exists."); | ||
} | ||
|
||
public T GetDataLoader<T>() where T : IDataLoader | ||
=> (T)_dataLoaders.GetOrAdd(CreateKey<T>(), _ => CreateDataLoader<T>()); | ||
|
||
private T CreateDataLoader<T>() where T : IDataLoader | ||
{ | ||
if (registrations.TryGetValue(typeof(T), out var registration)) | ||
{ | ||
return (T)registration.CreateDataLoader(serviceProvider); | ||
} | ||
|
||
var adHocRegistration = new DataLoaderRegistration(typeof(T)); | ||
return (T)adHocRegistration.CreateDataLoader(serviceProvider); | ||
} | ||
|
||
private static string CreateKey<T>() | ||
=> typeof(T).FullName ?? typeof(T).Name; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
76 changes: 76 additions & 0 deletions
76
src/GreenDonut/src/Core/Instrumentation/AggregateDataLoaderDiagnosticEventListener.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Threading.Tasks; | ||
|
||
namespace GreenDonut; | ||
|
||
internal class AggregateDataLoaderDiagnosticEventListener( | ||
IDataLoaderDiagnosticEventListener[] listeners) | ||
: DataLoaderDiagnosticEventListener | ||
{ | ||
public override void ResolvedTaskFromCache( | ||
IDataLoader dataLoader, | ||
TaskCacheKey cacheKey, | ||
Task task) | ||
{ | ||
for (var i = 0; i < listeners.Length; i++) | ||
{ | ||
listeners[i].ResolvedTaskFromCache(dataLoader, cacheKey, task); | ||
} | ||
} | ||
|
||
public override IDisposable ExecuteBatch<TKey>( | ||
IDataLoader dataLoader, | ||
IReadOnlyList<TKey> keys) | ||
{ | ||
var scopes = new IDisposable[listeners.Length]; | ||
|
||
for (var i = 0; i < listeners.Length; i++) | ||
{ | ||
scopes[i] = listeners[i].ExecuteBatch(dataLoader, keys); | ||
} | ||
|
||
return new AggregateEventScope(scopes); | ||
} | ||
|
||
public override void BatchResults<TKey, TValue>( | ||
IReadOnlyList<TKey> keys, | ||
ReadOnlySpan<Result<TValue>> values) | ||
{ | ||
for (var i = 0; i < listeners.Length; i++) | ||
{ | ||
listeners[i].BatchResults(keys, values); | ||
} | ||
} | ||
|
||
public override void BatchError<TKey>( | ||
IReadOnlyList<TKey> keys, | ||
Exception error) | ||
{ | ||
for (var i = 0; i < listeners.Length; i++) | ||
{ | ||
listeners[i].BatchError(keys, error); | ||
} | ||
} | ||
|
||
public override void BatchItemError<TKey>( | ||
TKey key, | ||
Exception error) | ||
{ | ||
for (var i = 0; i < listeners.Length; i++) | ||
{ | ||
listeners[i].BatchItemError(key, error); | ||
} | ||
} | ||
|
||
private sealed class AggregateEventScope(IDisposable[] scopes) : IDisposable | ||
{ | ||
public void Dispose() | ||
{ | ||
for (var i = 0; i < scopes.Length; i++) | ||
{ | ||
scopes[i].Dispose(); | ||
} | ||
} | ||
} | ||
} |
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.