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
Expand Up @@ -8,6 +8,14 @@ internal sealed partial class DeferExecutionCoordinator
private bool _isInitialized;
#endif

[Conditional("DEBUG")]
private void AssertInitialized()
{
#if DEBUG
Debug.Assert(_isInitialized);
#endif
}

/// <summary>
/// Initializes the coordinator for a new execution cycle.
/// Must be called before any other operations when leased from a pool.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public DeferExecutionCoordinator()
/// </summary>
public int Branch(int currentBranchId, Path path, DeferUsage deferUsage)
{
Debug.Assert(_isInitialized);
AssertInitialized();

var key = new DeferredBranchKey(path, deferUsage, currentBranchId);

Expand All @@ -74,7 +74,7 @@ public int Branch(int currentBranchId, Path path, DeferUsage deferUsage)
/// </summary>
public void EnqueueResult(OperationResult result)
{
Debug.Assert(_isInitialized);
AssertInitialized();

lock (_sync)
{
Expand All @@ -89,7 +89,7 @@ public void EnqueueResult(OperationResult result)
/// </summary>
public void EnqueueResult(OperationResult result, int branchId)
{
Debug.Assert(_isInitialized);
AssertInitialized();

lock (_sync)
{
Expand All @@ -110,7 +110,7 @@ public void EnqueueResult(OperationResult result, int branchId)
public async IAsyncEnumerable<OperationResult> ReadResultsAsync(
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
Debug.Assert(_isInitialized);
AssertInitialized();

List<OperationResult>? snapshot = null;
await using var registration = cancellationToken.Register(_signal.Set);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,11 +210,20 @@ public override async IAsyncEnumerable<SourceSchemaResult> ReadAsResultStreamAsy
case 1:
{
var result = await response.ReadAsResultAsync(cancellationToken);
var sourceSchemaResult = new SourceSchemaResult(variables[0].Path, result);
var variable = variables[0];
var sourceSchemaResult = new SourceSchemaResult(variable.Path, result);

configuration.OnSourceSchemaResult?.Invoke(context, node, sourceSchemaResult);

yield return sourceSchemaResult;

foreach (var additionalPath in variable.AdditionalPaths)
{
var alias = sourceSchemaResult.WithPath(additionalPath);
configuration.OnSourceSchemaResult?.Invoke(context, node, alias);
yield return alias;
}

break;
}

Expand All @@ -228,14 +237,20 @@ public override async IAsyncEnumerable<SourceSchemaResult> ReadAsResultStreamAsy
await foreach (var result in response.ReadAsResultStreamAsync()
.WithCancellation(cancellationToken))
{
var (path, _) = variables[requestIndex];

var sourceSchemaResult = new SourceSchemaResult(path, result);
var variable = variables[requestIndex];
var sourceSchemaResult = new SourceSchemaResult(variable.Path, result);

configuration.OnSourceSchemaResult?.Invoke(context, node, sourceSchemaResult);

yield return sourceSchemaResult;

foreach (var additionalPath in variable.AdditionalPaths)
{
var alias = sourceSchemaResult.WithPath(additionalPath);
configuration.OnSourceSchemaResult?.Invoke(context, node, alias);
yield return alias;
}

requestIndex++;
}
}
Expand All @@ -253,23 +268,47 @@ public override async IAsyncEnumerable<SourceSchemaResult> ReadAsResultStreamAsy
}

var index = variableIndex.GetInt32();
var (path, _) = variables[index];
var sourceSchemaResult = new SourceSchemaResult(path, result);
var variable = variables[index];
var sourceSchemaResult = new SourceSchemaResult(variable.Path, result);

configuration.OnSourceSchemaResult?.Invoke(context, node, sourceSchemaResult);

yield return sourceSchemaResult;

foreach (var additionalPath in variable.AdditionalPaths)
{
var alias = sourceSchemaResult.WithPath(additionalPath);
configuration.OnSourceSchemaResult?.Invoke(context, node, alias);
yield return alias;
}
}
}

if (errorResult is not null)
{
yield return errorResult;

foreach (var additionalPath in variables[0].AdditionalPaths)
{
var alias = errorResult.WithPath(additionalPath);
configuration.OnSourceSchemaResult?.Invoke(context, node, alias);
yield return alias;
}

for (var i = 1; i < variables.Length; i++)
{
var (path, _) = variables[i];
yield return new SourceSchemaResult(path, SourceResultDocument.CreateEmptyObject());
var variable = variables[i];
var sourceSchemaResult = new SourceSchemaResult(
variable.Path,
SourceResultDocument.CreateEmptyObject());
yield return sourceSchemaResult;

foreach (var additionalPath in variable.AdditionalPaths)
{
var alias = sourceSchemaResult.WithPath(additionalPath);
configuration.OnSourceSchemaResult?.Invoke(context, node, alias);
yield return alias;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,27 @@ public sealed class SourceSchemaResult : IDisposable
private static ReadOnlySpan<byte> ErrorsProperty => "errors"u8;
private static ReadOnlySpan<byte> ExtensionsProperty => "extensions"u8;
private readonly SourceResultDocument _document;
private readonly bool _ownsDocument;

public SourceSchemaResult(
Path path,
SourceResultDocument document,
FinalMessage final = FinalMessage.Undefined)
: this(path, document, final, ownsDocument: true)
{
}

private SourceSchemaResult(
Path path,
SourceResultDocument document,
FinalMessage final,
bool ownsDocument)
{
ArgumentNullException.ThrowIfNull(path);
ArgumentNullException.ThrowIfNull(document);

_document = document;
_ownsDocument = ownsDocument;
Path = path;
Final = final;
}
Expand Down Expand Up @@ -60,5 +71,13 @@ public SourceResultElement Extensions

public FinalMessage Final { get; }

public void Dispose() => _document.Dispose();
internal SourceSchemaResult WithPath(Path path) => new(path, _document, Final, ownsDocument: false);

public void Dispose()
{
if (_ownsDocument)
{
_document.Dispose();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,14 @@ protected override async ValueTask<ExecutionStatus> OnExecuteAsync(
context.TrackSourceSchemaClientResponse(this, response);

// we read the responses from the response stream.
bufferLength = Math.Max(variables.Length, 1);
var totalPathCount = variables.Length;

for (var i = 0; i < variables.Length; i++)
{
totalPathCount += variables[i].AdditionalPaths.Length;
}

bufferLength = Math.Max(totalPathCount, 1);
buffer = ArrayPool<SourceSchemaResult>.Shared.Rent(bufferLength);

await foreach (var result in response.ReadAsResultStreamAsync(cancellationToken))
Expand Down Expand Up @@ -264,14 +271,27 @@ private static void AddErrors(
}
else
{
var pathBufferLength = variables.Length;
var pathBufferLength = 0;

for (var i = 0; i < variables.Length; i++)
{
pathBufferLength += 1 + variables[i].AdditionalPaths.Length;
}

var pathBuffer = ArrayPool<Path>.Shared.Rent(pathBufferLength);

try
{
var pathBufferIndex = 0;

for (var i = 0; i < variables.Length; i++)
{
pathBuffer[i] = variables[i].Path;
pathBuffer[pathBufferIndex++] = variables[i].Path;

foreach (var additionalPath in variables[i].AdditionalPaths)
{
pathBuffer[pathBufferIndex++] = additionalPath;
}
}

context.AddErrors(error, responseNames, pathBuffer.AsSpan(0, pathBufferLength));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,8 @@ public ImmutableArray<VariableValues> CreateVariableValueSets(

PooledArrayWriter? buffer = null;
VariableValues[]? variableValueSets = null;
Dictionary<ObjectValueNode, int>? seen = null;
List<Path>?[]? additionalPaths = null;
var nextIndex = 0;

foreach (var result in current)
Expand All @@ -372,10 +374,44 @@ public ImmutableArray<VariableValues> CreateVariableValueSets(
requiredData,
ref buffer);

if (variables is not null)
if (variables is null)
{
variableValueSets ??= new VariableValues[current.Count];
variableValueSets[nextIndex++] = new VariableValues(result.Path, variables);
continue;
}

variableValueSets ??= new VariableValues[current.Count];

if (nextIndex > 0)
{
seen ??= new Dictionary<ObjectValueNode, int>(VariableValueComparer.Instance)
{
[variableValueSets[0].Values] = 0
};

if (seen.TryGetValue(variables, out var existingIndex))
{
additionalPaths ??= new List<Path>?[current.Count];
(additionalPaths[existingIndex] ??= []).Add(result.Path);
continue;
}

seen[variables] = nextIndex;
}

variableValueSets[nextIndex++] = new VariableValues(result.Path, variables);
}

if (additionalPaths is not null)
{
for (var i = 0; i < nextIndex; i++)
{
if (additionalPaths[i] is { } paths)
{
variableValueSets![i] = variableValueSets[i] with
{
AdditionalPaths = [.. paths]
};
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using HotChocolate.Language;

namespace HotChocolate.Fusion.Execution.Results;

internal sealed class VariableValueComparer : IEqualityComparer<ObjectValueNode>
{
public static readonly VariableValueComparer Instance = new();

public bool Equals(ObjectValueNode? x, ObjectValueNode? y)
{
if (ReferenceEquals(x, y))
{
return true;
}

if (x is null || y is null)
{
return false;
}

var xFields = x.Fields;
var yFields = y.Fields;

if (xFields.Count != yFields.Count)
{
return false;
}

// MapRequirements always creates fields in deterministic order.
// We only compare values, as names are equivalent for a given operation node.
for (var i = 0; i < xFields.Count; i++)
{
if (!SyntaxComparer.BySyntax.Equals(xFields[i].Value, yFields[i].Value))
{
return false;
}
}

return true;
}

public int GetHashCode(ObjectValueNode obj)
{
ArgumentNullException.ThrowIfNull(obj);

var hashCode = new HashCode();

// MapRequirements creates a deterministic field order, so no field sorting is needed.
for (var i = 0; i < obj.Fields.Count; i++)
{
hashCode.Add(SyntaxComparer.BySyntax.GetHashCode(obj.Fields[i].Value));
}

return hashCode.ToHashCode();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
using System.Collections.Immutable;
using HotChocolate.Language;

namespace HotChocolate.Fusion.Execution;

public sealed record VariableValues(Path Path, ObjectValueNode Values);
public sealed record VariableValues(Path Path, ObjectValueNode Values)
{
/// <summary>
/// Gets the additional paths that share the same variable values as the primary <see cref="Path"/>.
/// </summary>
public ImmutableArray<Path> AdditionalPaths { get; init; } = [];
}
Original file line number Diff line number Diff line change
Expand Up @@ -223,12 +223,6 @@ sourceSchemas:
{
"__fusion_1_id": 1
},
{
"__fusion_1_id": 2
},
{
"__fusion_1_id": 2
},
{
"__fusion_1_id": 2
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,12 +231,6 @@ sourceSchemas:
{
"__fusion_1_id": 1
},
{
"__fusion_1_id": 2
},
{
"__fusion_1_id": 2
},
{
"__fusion_1_id": 2
}
Expand Down
Loading
Loading