Skip to content

Commit 0e318cd

Browse files
authored
[Fusion] Added mutation support to planner. (#8478)
1 parent 606a1d5 commit 0e318cd

File tree

74 files changed

+634
-171
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+634
-171
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace HotChocolate.Fusion.Execution;
2+
3+
internal enum ExecutionStrategy
4+
{
5+
/// <summary>
6+
/// Everything that can be executed in parallel will be executed in parallel.
7+
/// </summary>
8+
Parallel,
9+
10+
/// <summary>
11+
/// Root nodes will be executed in sequence, everything else can be parallelized.
12+
/// </summary>
13+
SequentialRoots
14+
}

src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Execution/Nodes/Operation.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public sealed class Operation
1919

2020
internal Operation(
2121
string id,
22+
string hash,
2223
OperationDefinitionNode definition,
2324
IObjectTypeDefinition rootType,
2425
ISchemaDefinition schema,
@@ -35,6 +36,7 @@ internal Operation(
3536
ArgumentNullException.ThrowIfNull(includeConditions);
3637

3738
Id = id;
39+
Hash = hash;
3840
Definition = definition;
3941
RootType = rootType;
4042
Schema = schema;
@@ -51,6 +53,11 @@ internal Operation(
5153
/// </summary>
5254
public string Id { get; }
5355

56+
/// <summary>
57+
/// Gets the hash of the original operation document.
58+
/// </summary>
59+
public string Hash { get; }
60+
5461
/// <summary>
5562
/// Gets the name of the operation.
5663
/// </summary>

src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Execution/Nodes/OperationCompiler.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public OperationCompiler(
2424
_typeNameField = new TypeNameField(nonNullStringType);
2525
}
2626

27-
public Operation Compile(string id, OperationDefinitionNode operationDefinition)
27+
public Operation Compile(string id, string hash, OperationDefinitionNode operationDefinition)
2828
{
2929
ArgumentException.ThrowIfNullOrWhiteSpace(id);
3030
ArgumentNullException.ThrowIfNull(operationDefinition);
@@ -53,6 +53,7 @@ public Operation Compile(string id, OperationDefinitionNode operationDefinition)
5353

5454
return new Operation(
5555
id,
56+
hash,
5657
operationDefinition,
5758
rootType,
5859
_schema,

src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Execution/Nodes/Serialization/JsonOperationPlanFormatter.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ private static void WriteOperationNode(
112112
{
113113
jsonWriter.WriteStartObject();
114114
jsonWriter.WriteNumber("id", node.Id);
115+
jsonWriter.WriteString("type", "Operation");
115116
jsonWriter.WriteString("schema", node.SchemaName);
116117
jsonWriter.WriteString("operation", node.Operation.ToString(indented: true));
117118

@@ -182,6 +183,7 @@ private static void WriteIntrospectionNode(
182183
{
183184
jsonWriter.WriteStartObject();
184185
jsonWriter.WriteNumber("id", selection.Id);
186+
jsonWriter.WriteString("type", "Introspection");
185187
jsonWriter.WriteString("responseName", selection.ResponseName);
186188
jsonWriter.WriteString("fieldName", selection.Field.Name);
187189
jsonWriter.WriteEndObject();

src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Execution/OperationPlanExecutor.cs

Lines changed: 95 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System.Collections.Immutable;
22
using System.Runtime.CompilerServices;
3-
using System.Runtime.InteropServices;
43
using HotChocolate.Execution;
54
using HotChocolate.Fusion.Execution.Nodes;
65
using HotChocolate.Language;
@@ -14,10 +13,18 @@ public async Task<IExecutionResult> ExecuteAsync(
1413
CancellationToken cancellationToken = default)
1514
{
1615
context.Begin();
17-
await ExecutorSession.ExecuteAsync(context, cancellationToken);
16+
var strategy = DetermineExecutionStrategy(context);
17+
await ExecutorSession.ExecuteAsync(context, strategy, cancellationToken);
1818
return context.Complete();
1919
}
2020

21+
private static ExecutionStrategy DetermineExecutionStrategy(OperationPlanContext context)
22+
=> context.OperationPlan.Operation.Definition.Operation switch
23+
{
24+
OperationType.Mutation => ExecutionStrategy.SequentialRoots,
25+
_ => ExecutionStrategy.Parallel
26+
};
27+
2128
private sealed class ExecutorSession
2229
{
2330
private readonly List<ExecutionNode> _stack = [];
@@ -29,33 +36,96 @@ private sealed class ExecutorSession
2936
private readonly OperationPlan _plan;
3037
private readonly ImmutableArray<ExecutionNodeTrace>.Builder? _traces;
3138
private readonly CancellationToken _cancellationToken;
39+
private readonly ExecutionStrategy _strategy;
40+
private int _nextRootNode;
3241

33-
private ExecutorSession(OperationPlanContext context, CancellationToken cancellationToken)
42+
private ExecutorSession(OperationPlanContext context, ExecutionStrategy strategy, CancellationToken cancellationToken)
3443
{
3544
_context = context;
45+
_strategy = strategy;
3646
_cancellationToken = cancellationToken;
3747
_plan = context.OperationPlan;
3848
_backlog = [.. context.OperationPlan.AllNodes];
49+
50+
// For sequential execution (mutations), remove root nodes from backlog initially
51+
if (_strategy == ExecutionStrategy.SequentialRoots)
52+
{
53+
foreach (var root in context.OperationPlan.RootNodes)
54+
{
55+
_backlog.Remove(root);
56+
}
57+
}
58+
3959
var collectTracing = context.Schema.GetRequestOptions().CollectOperationPlanTelemetry;
4060
_traces = collectTracing ? ImmutableArray.CreateBuilder<ExecutionNodeTrace>() : null;
4161
}
4262

43-
public static Task ExecuteAsync(OperationPlanContext context, CancellationToken cancellationToken)
44-
=> new ExecutorSession(context, cancellationToken).ExecuteInternalAsync();
63+
public static Task ExecuteAsync(OperationPlanContext context, ExecutionStrategy strategy, CancellationToken cancellationToken)
64+
=> new ExecutorSession(context, strategy, cancellationToken).ExecuteInternalAsync();
4565

4666
private async Task ExecuteInternalAsync()
4767
{
48-
Start();
68+
if (_strategy == ExecutionStrategy.Parallel)
69+
{
70+
await ExecuteQueryAsync();
71+
}
72+
else
73+
{
74+
await ExecuteMutationAsync();
75+
}
76+
77+
if (_traces is { Count: > 0 })
78+
{
79+
_context.Traces = [.. _traces];
80+
}
81+
}
82+
83+
private async Task ExecuteQueryAsync()
84+
{
85+
// Start all root nodes immediately for parallel execution
86+
StartAllRootNodes();
4987

88+
// Process until all nodes complete
5089
while (IsProcessing())
5190
{
5291
await WaitForNextCompletionAsync();
5392
EnqueueNextNodes();
5493
}
5594
}
5695

57-
private ReadOnlySpan<ExecutionNode> WaitingToRun
58-
=> CollectionsMarshal.AsSpan(_backlog);
96+
private async Task ExecuteMutationAsync()
97+
{
98+
// Sequential root processing - one root at a time
99+
while (StartNextRootNode())
100+
{
101+
// Complete the entire subtree of current root before starting next
102+
var enqueued = true;
103+
while (enqueued)
104+
{
105+
await WaitForNextCompletionAsync();
106+
enqueued = EnqueueNextNodes();
107+
}
108+
}
109+
}
110+
111+
private void StartAllRootNodes()
112+
{
113+
foreach (var node in _context.OperationPlan.RootNodes)
114+
{
115+
StartNode(node);
116+
}
117+
}
118+
119+
private bool StartNextRootNode()
120+
{
121+
var roots = _context.OperationPlan.RootNodes;
122+
if (_nextRootNode < roots.Length)
123+
{
124+
StartNode(roots[_nextRootNode++]);
125+
return true;
126+
}
127+
return false;
128+
}
59129

60130
[MethodImpl(MethodImplOptions.AggressiveInlining)]
61131
private bool IsProcessing() => _backlog.Count > 0 || _activeTasks.Count > 0;
@@ -69,7 +139,7 @@ private void SkipNode(ExecutionNode node)
69139
{
70140
_backlog.Remove(current);
71141

72-
foreach (var enqueuedNode in WaitingToRun)
142+
foreach (var enqueuedNode in _backlog)
73143
{
74144
if (enqueuedNode.Dependencies.Contains(current))
75145
{
@@ -79,14 +149,6 @@ private void SkipNode(ExecutionNode node)
79149
}
80150
}
81151

82-
private void Start()
83-
{
84-
foreach (var node in _context.OperationPlan.RootNodes)
85-
{
86-
StartNode(node);
87-
}
88-
}
89-
90152
[MethodImpl(MethodImplOptions.AggressiveInlining)]
91153
private void StartNode(ExecutionNode node)
92154
{
@@ -109,7 +171,7 @@ private async Task WaitForNextCompletionAsync()
109171
_traces?.Add(new ExecutionNodeTrace
110172
{
111173
Id = task.Result.Id,
112-
SpanId = task.Result.Activity?.Id,
174+
SpanId = task.Result.Activity?.SpanId.ToHexString(),
113175
Status = task.Result.Status,
114176
Duration = task.Result.Duration
115177
});
@@ -122,7 +184,7 @@ private async Task WaitForNextCompletionAsync()
122184
else if (task.IsFaulted || task.IsCanceled)
123185
{
124186
// execution nodes are not expected to throw as exception should be handled within.
125-
// if they do its a fatal error for the execution, so we await failed task here
187+
// if they do it's a fatal error for the execution, so we await failed task here
126188
// so that they can throw and terminate the execution.
127189
await task;
128190
}
@@ -134,16 +196,14 @@ private async Task WaitForNextCompletionAsync()
134196
}
135197

136198
_completedTasks.Clear();
137-
138-
if (_backlog.Count == 0 && _traces is { Count: > 0 })
139-
{
140-
_context.Traces = [.. _traces];
141-
}
142199
}
143200

144-
private void EnqueueNextNodes()
201+
private bool EnqueueNextNodes()
145202
{
146-
foreach (var node in WaitingToRun)
203+
var enqueued = false;
204+
_stack.Clear();
205+
206+
foreach (var node in _backlog)
147207
{
148208
var dependenciesFulfilled = true;
149209

@@ -159,9 +219,17 @@ private void EnqueueNextNodes()
159219

160220
if (dependenciesFulfilled)
161221
{
162-
StartNode(node);
222+
_stack.Push(node);
223+
enqueued = true;
163224
}
164225
}
226+
227+
foreach (var node in _stack)
228+
{
229+
StartNode(node);
230+
}
231+
232+
return enqueued;
165233
}
166234
}
167235
}

src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Execution/Pipeline/OperationPlanMiddleware.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,11 @@ public ValueTask InvokeAsync(RequestContext context, RequestDelegate next)
3636

3737
// Before we can plan an operation, we must defragmentize it and remove statical include conditions.
3838
var operationId = context.GetOperationId();
39+
var operationHash = context.OperationDocumentInfo.Hash.Value;
40+
var operationShortHash = operationHash[..8];
3941
var rewritten = _rewriter.RewriteDocument(operationDocumentInfo.Document, context.Request.OperationName);
4042
var operation = GetOperation(rewritten);
41-
var executionPlan = _planner.CreatePlan(operationId, operation);
43+
var executionPlan = _planner.CreatePlan(operationId, operationHash, operationShortHash, operation);
4244
context.SetOperationPlan(executionPlan);
4345

4446
return next(context);

src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Execution/Results/ValueCompletion.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@ private bool TryCompleteAbstractValue(
273273
depth,
274274
parent);
275275

276+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
276277
private IObjectTypeDefinition GetType(IType type, JsonElement data)
277278
{
278279
var namedType = type.NamedType();
@@ -286,6 +287,7 @@ private IObjectTypeDefinition GetType(IType type, JsonElement data)
286287
return _schema.Types.GetType<IObjectTypeDefinition>(typeName);
287288
}
288289

290+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
289291
private void AssertDepthAllowed(ref int depth)
290292
{
291293
depth++;
@@ -299,6 +301,7 @@ private void AssertDepthAllowed(ref int depth)
299301

300302
file static class ValueCompletionExtensions
301303
{
304+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
302305
public static bool IsNullOrUndefined(this JsonElement element)
303306
=> element.ValueKind is JsonValueKind.Null or JsonValueKind.Undefined;
304307
}

0 commit comments

Comments
 (0)