Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Buffers;
using System.Collections.Immutable;
using System.Runtime.CompilerServices;
using HotChocolate.Features;

namespace HotChocolate.Execution;
Expand All @@ -9,8 +10,8 @@ namespace HotChocolate.Execution;
/// </summary>
public abstract class ExecutionResult : IExecutionResult
{
private static readonly ArrayPool<Func<ValueTask>> s_cleanUpTaskPool = ArrayPool<Func<ValueTask>>.Shared;
private Func<ValueTask>[] _cleanUpTasks = [];
private static readonly ArrayPool<CleanupEntry> s_cleanUpTaskPool = ArrayPool<CleanupEntry>.Shared;
private CleanupEntry[] _cleanUpTasks = [];
private int _cleanupTasksLength;
private bool _disposed;

Expand All @@ -27,29 +28,47 @@ public abstract class ExecutionResult : IExecutionResult
/// <inheritdoc cref="IFeatureProvider" />
public IFeatureCollection Features { get; } = new FeatureCollection();

/// <inheritdoc cref="IExecutionResult" />
public void RegisterForCleanup(Func<ValueTask> clean)
/// <summary>
/// Registers a resource that needs to be disposed when the result is being disposed.
/// </summary>
/// <param name="disposable">
/// The resource that needs to be disposed.
/// </param>
public void RegisterForCleanup(IDisposable disposable)
{
ArgumentNullException.ThrowIfNull(clean);

if (_cleanUpTasks.Length == 0)
{
_cleanUpTasks = s_cleanUpTaskPool.Rent(8);
_cleanupTasksLength = 0;
}
else if (_cleanupTasksLength >= _cleanUpTasks.Length)
{
var buffer = s_cleanUpTaskPool.Rent(_cleanupTasksLength * 2);
var currentBuffer = _cleanUpTasks.AsSpan();
ArgumentNullException.ThrowIfNull(disposable);
AddCleanupEntry(new CleanupEntry { Target = disposable, Kind = CleanupKind.Disposable });
}

currentBuffer.CopyTo(buffer);
currentBuffer.Clear();
s_cleanUpTaskPool.Return(_cleanUpTasks);
/// <summary>
/// Registers a cleanup action to be executed when the result is disposed.
/// </summary>
/// <param name="clean">
/// A cleanup action that will be executed when this result is disposed.
/// </param>
public void RegisterForCleanup(Action clean)
{
ArgumentNullException.ThrowIfNull(clean);
AddCleanupEntry(new CleanupEntry { Target = clean, Kind = CleanupKind.Action });
}

_cleanUpTasks = buffer;
}
/// <summary>
/// Registers a resource that needs to be disposed asynchronously when the result is being disposed.
/// </summary>
/// <param name="disposable">
/// The resource that needs to be disposed.
/// </param>
public void RegisterForCleanup(IAsyncDisposable disposable)
{
ArgumentNullException.ThrowIfNull(disposable);
AddCleanupEntry(new CleanupEntry { Target = disposable, Kind = CleanupKind.AsyncDisposable });
}

_cleanUpTasks[_cleanupTasksLength++] = clean;
/// <inheritdoc cref="IExecutionResult" />
public void RegisterForCleanup(Func<ValueTask> clean)
{
ArgumentNullException.ThrowIfNull(clean);
AddCleanupEntry(new CleanupEntry { Target = clean, Kind = CleanupKind.FuncValueTask });
}

/// <summary>
Expand All @@ -64,18 +83,73 @@ public async ValueTask DisposeAsync()
{
if (_cleanupTasksLength > 0)
{
var tasks = _cleanUpTasks;
var entries = _cleanUpTasks;

for (var i = 0; i < _cleanupTasksLength; i++)
{
await tasks[i]().ConfigureAwait(false);
switch (entries[i].Kind)
{
case CleanupKind.FuncValueTask:
await Unsafe.As<Func<ValueTask>>(entries[i].Target).Invoke().ConfigureAwait(false);
break;

case CleanupKind.Disposable:
Unsafe.As<IDisposable>(entries[i].Target).Dispose();
break;

case CleanupKind.AsyncDisposable:
await Unsafe.As<IAsyncDisposable>(entries[i].Target).DisposeAsync().ConfigureAwait(false);
break;

case CleanupKind.Action:
Unsafe.As<Action>(entries[i].Target).Invoke();
break;
}
}

tasks.AsSpan(0, _cleanupTasksLength).Clear();
s_cleanUpTaskPool.Return(tasks);
entries.AsSpan(0, _cleanupTasksLength).Clear();
s_cleanUpTaskPool.Return(entries);
}

_disposed = true;
}

GC.SuppressFinalize(this);
}

private void AddCleanupEntry(CleanupEntry entry)
{
if (_cleanUpTasks.Length == 0)
{
_cleanUpTasks = s_cleanUpTaskPool.Rent(8);
_cleanupTasksLength = 0;
}
else if (_cleanupTasksLength >= _cleanUpTasks.Length)
{
var buffer = s_cleanUpTaskPool.Rent(_cleanupTasksLength * 2);
var currentBuffer = _cleanUpTasks.AsSpan();

currentBuffer.CopyTo(buffer);
currentBuffer.Clear();
s_cleanUpTaskPool.Return(_cleanUpTasks);

_cleanUpTasks = buffer;
}

_cleanUpTasks[_cleanupTasksLength++] = entry;
}

private enum CleanupKind
{
FuncValueTask = 0,
Disposable = 1,
AsyncDisposable = 2,
Action = 3
}

private struct CleanupEntry
{
public object Target;
public CleanupKind Kind;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,49 +52,6 @@ public ResponseStream ExpectResponseStream()

extension(IExecutionResult result)
{
/// <summary>
/// Registers a cleanup task for execution resources bound to this execution result.
/// </summary>
/// <param name="clean">
/// A cleanup task that will be executed when this result is disposed.
/// </param>
public void RegisterForCleanup(Action clean)
{
ArgumentNullException.ThrowIfNull(clean);

result.RegisterForCleanup(() =>
{
clean();
return default;
});
}

/// <summary>
/// Registers a resource that needs to be disposed when the result is being disposed.
/// </summary>
/// <param name="disposable">
/// The resource that needs to be disposed.
/// </param>
public void RegisterForCleanup(IDisposable disposable)
{
ArgumentNullException.ThrowIfNull(disposable);

result.RegisterForCleanup(disposable.Dispose);
}

/// <summary>
/// Registers a resource that needs to be disposed when the result is being disposed.
/// </summary>
/// <param name="disposable">
/// The resource that needs to be disposed.
/// </param>
public void RegisterForCleanup(IAsyncDisposable disposable)
{
ArgumentNullException.ThrowIfNull(disposable);

result.RegisterForCleanup(disposable.DisposeAsync);
}

/// <summary>
/// Defines if the specified <see cref="IExecutionResult"/> is a response stream.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,50 @@ public interface IExecutionResult : IFeatureProvider, IAsyncDisposable
set => Features.Set(value);
}

/// <summary>
/// Registers a resource that needs to be disposed when the result is being disposed.
/// </summary>
/// <param name="disposable">
/// The resource that needs to be disposed.
/// </param>
void RegisterForCleanup(IDisposable disposable)
{
ArgumentNullException.ThrowIfNull(disposable);
RegisterForCleanup(() =>
{
disposable.Dispose();
return default;
});
}

/// <summary>
/// Registers a cleanup action to be executed when the result is disposed.
/// </summary>
/// <param name="clean">
/// A cleanup action that will be executed when this result is disposed.
/// </param>
void RegisterForCleanup(Action clean)
{
ArgumentNullException.ThrowIfNull(clean);
RegisterForCleanup(() =>
{
clean();
return default;
});
}

/// <summary>
/// Registers a resource that needs to be disposed asynchronously when the result is being disposed.
/// </summary>
/// <param name="disposable">
/// The resource that needs to be disposed.
/// </param>
void RegisterForCleanup(IAsyncDisposable disposable)
{
ArgumentNullException.ThrowIfNull(disposable);
RegisterForCleanup(disposable.DisposeAsync);
}

/// <summary>
/// Registers a cleanup task for execution resources bound to this execution result.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,7 @@ public OperationResult(

if (data.MemoryHolder is { } memoryHolder)
{
RegisterForCleanup(() =>
{
memoryHolder.Dispose();
return ValueTask.CompletedTask;
});
RegisterForCleanup(memoryHolder);
}
}

Expand Down
12 changes: 6 additions & 6 deletions src/HotChocolate/Core/src/Execution.Abstractions/Path.cs
Original file line number Diff line number Diff line change
Expand Up @@ -263,12 +263,12 @@ public IReadOnlyList<object> ToList()
}

/// <summary>
/// Creates a new list representing the current <see cref="Path"/>.
/// Copies the segments of the current <see cref="Path"/> into the provided span.
/// </summary>
/// <returns>
/// Returns a new list representing the current <see cref="Path"/>.
/// </returns>
public void ToList(Span<object> path)
/// <param name="path">
/// The destination span. Must be at least <see cref="Length"/> elements long.
/// </param>
Comment thread
michaelstaib marked this conversation as resolved.
public void CopyTo(Span<object> path)
{
if (IsRoot)
{
Comment thread
michaelstaib marked this conversation as resolved.
Expand All @@ -283,7 +283,7 @@ public void ToList(Span<object> path)
}

var current = this;
var length = path.Length;
var length = Length;

while (!current.IsRoot)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public void Register_One_Async_Cleanup_Func_Func_is_Null()
var result = new ResponseStream(() => null!);

// act
void Fail() => result.RegisterForCleanup(null!);
void Fail() => result.RegisterForCleanup(default(Func<ValueTask>)!);

// assert
Assert.Throws<ArgumentNullException>(Fail);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public async Task Document_Cache_Should_Not_Be_Scoped_To_Executor()
{
// arrange
var executorEvictedResetEvent = new ManualResetEventSlim(false);
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));

var services =
new ServiceCollection()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -368,19 +368,19 @@ private static bool TryGetResultPath(
SourceSchemaClientRequest request,
int variableIndex,
out CompactPath path,
out ImmutableArray<CompactPath> additionalPaths)
out CompactPathSegment additionalPaths)
{
if (request.Variables.Length == 0)
{
path = CompactPath.Root;
additionalPaths = [];
additionalPaths = default;
return true;
}

if ((uint)variableIndex >= (uint)request.Variables.Length)
{
path = CompactPath.Root;
additionalPaths = [];
additionalPaths = default;
return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ namespace HotChocolate.Fusion.Execution.Clients;
/// </summary>
public sealed class SourceSchemaErrors
{
private static readonly ArrayPool<object> s_objectPool = ArrayPool<object>.Shared;
/// <summary>
/// Gets the collection of errors that are not associated with specific GraphQL field paths.
/// </summary>
Expand Down Expand Up @@ -64,9 +65,9 @@ public sealed class SourceSchemaErrors
continue;
}

var rented = ArrayPool<object>.Shared.Rent(error.Path.Length);
var rented = s_objectPool.Rent(error.Path.Length);
var pathSegments = rented.AsSpan(0, error.Path.Length);
error.Path.ToList(pathSegments);
error.Path.CopyTo(pathSegments);
var lastPathIndex = pathSegments.Length - 1;

try
Expand Down Expand Up @@ -95,7 +96,7 @@ public sealed class SourceSchemaErrors
finally
{
pathSegments.Clear();
ArrayPool<object>.Shared.Return(rented);
s_objectPool.Return(rented);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -669,19 +669,19 @@ private static bool TryGetResultPath(
SourceSchemaClientRequest request,
int variableIndex,
out CompactPath path,
out ImmutableArray<CompactPath> additionalPaths)
out CompactPathSegment additionalPaths)
{
if (request.Variables.Length == 0)
{
path = CompactPath.Root;
additionalPaths = [];
additionalPaths = default;
return true;
}

if ((uint)variableIndex >= (uint)request.Variables.Length)
{
path = CompactPath.Root;
additionalPaths = [];
additionalPaths = default;
return false;
}

Expand Down
Loading
Loading