/// <summary> /// Creates specified child execution nodes of an object execution node. /// </summary> private static void SetSubFieldNodes(ExecutionContext context, ObjectExecutionNode parent, Fields fields) { var parentType = parent.GetObjectGraphType(context.Schema); var subFields = new ExecutionNode[fields.Count]; int i = 0; foreach (var kvp in fields) { var field = kvp.Value; var fieldDefinition = ExecutionHelper.GetFieldDefinition(context.Schema, parentType, field); if (fieldDefinition == null) { throw new InvalidOperationException($"Schema is not configured correctly to fetch field '{field.Name}' from type '{parentType.Name}'."); } var node = BuildExecutionNode(parent, fieldDefinition.ResolvedType, field, fieldDefinition); subFields[i++] = node; } parent.SubFields = subFields; }
protected async Task ExecuteNodeTreeAsync(ExecutionContext context, ExecutionNode rootNode) { var pendingNodes = new List <ExecutionNode> { rootNode }; while (pendingNodes.Count > 0) { var currentTasks = new Task <ExecutionNode> [pendingNodes.Count]; // Start executing all pending nodes for (int i = 0; i < pendingNodes.Count; i++) { context.CancellationToken.ThrowIfCancellationRequested(); currentTasks[i] = ExecuteNodeAsync(context, pendingNodes[i]); } pendingNodes.Clear(); await OnBeforeExecutionStepAwaitedAsync(context) .ConfigureAwait(false); // Await tasks for this execution step var completedNodes = await Task.WhenAll(currentTasks) .ConfigureAwait(false); // Add child nodes to pending nodes to execute the next level in parallel var childNodes = completedNodes .OfType <IParentExecutionNode>() .SelectMany(x => x.GetChildNodes()); pendingNodes.AddRange(childNodes); } }
protected virtual void ValidateNodeResult(ExecutionContext context, ExecutionNode node) { var result = node.Result; // Validate result IGraphType fieldType = node.FieldDefinition.ResolvedType; var objectType = fieldType as IObjectGraphType; if (fieldType is NonNullGraphType nonNullType) { objectType = nonNullType?.ResolvedType as IObjectGraphType; if (result == null) { var type = nonNullType.ResolvedType; var error = new ExecutionError($"Cannot return null for non-null type. Field: {node.Name}, Type: {type.Name}!."); error.AddLocation(node.Field, context.Document); throw error; } } if (result == null) { return; } if (fieldType is IAbstractGraphType abstractType) { objectType = abstractType.GetObjectType(result); if (objectType == null) { var error = new ExecutionError( $"Abstract type {abstractType.Name} must resolve to an Object type at " + $"runtime for field {node.Parent.GraphType.Name}.{node.Name} " + $"with value {result}, received 'null'."); error.AddLocation(node.Field, context.Document); throw error; } if (!abstractType.IsPossibleType(objectType)) { var error = new ExecutionError($"Runtime Object type \"{objectType}\" is not a possible type for \"{abstractType}\""); error.AddLocation(node.Field, context.Document); throw error; } } if (objectType?.IsTypeOf != null && !objectType.IsTypeOf(result)) { var error = new ExecutionError($"Expected value of type \"{objectType}\" but got: {result}."); error.AddLocation(node.Field, context.Document); throw error; } }
public static ExecutionNode BuildExecutionNode(ExecutionNode parent, IGraphType graphType, Field field, FieldType fieldDefinition, string[] path = null) { path = path ?? AppendPath(parent.Path, field.Name); if (graphType is NonNullGraphType nonNullFieldType) { graphType = nonNullFieldType.ResolvedType; } switch (graphType) { case ListGraphType listGraphType: return(new ArrayExecutionNode(parent, graphType, field, fieldDefinition, path)); case IObjectGraphType objectGraphType: return(new ObjectExecutionNode(parent, graphType, field, fieldDefinition, path)); case IAbstractGraphType abstractType: return(new ObjectExecutionNode(parent, graphType, field, fieldDefinition, path)); case ScalarGraphType scalarType: return(new ValueExecutionNode(parent, graphType, field, fieldDefinition, path)); default: throw new InvalidOperationException($"Unexpected type: {graphType}"); } }
private Job(JobsCounter counter, ExecutionContext context, ExecutionNode node) { _counter = counter; _counter.Increment(); Context = context; Node = node; }
protected async Task ExecuteNodeTreeAsync(ExecutionContext context, ExecutionNode rootNode) { var pendingNodes = new Queue <ExecutionNode>(); pendingNodes.Enqueue(rootNode); var currentTasks = new List <Task <ExecutionNode> >(); while (pendingNodes.Count > 0) { // Start executing pending nodes, while limiting the maximum number of parallel executed nodes to the set limit while ((context.MaxParallelExecutionCount == null || currentTasks.Count < context.MaxParallelExecutionCount) && pendingNodes.Count > 0) { context.CancellationToken.ThrowIfCancellationRequested(); var pendingNode = pendingNodes.Dequeue(); var pendingNodeTask = ExecuteNodeAsync(context, pendingNode); if (pendingNodeTask.IsCompleted) { // Node completed synchronously, so no need to add it to the list of currently executing nodes // instead add any child nodes to the pendingNodes queue directly here var result = await pendingNodeTask; if (result is IParentExecutionNode parentExecutionNode) { foreach (var childNode in parentExecutionNode.GetChildNodes()) { pendingNodes.Enqueue(childNode); } } } else { // Node is actually asynchronous, so add it to the list of current tasks being executed in parallel currentTasks.Add(pendingNodeTask); } } await OnBeforeExecutionStepAwaitedAsync(context) .ConfigureAwait(false); // Await tasks for this execution step var completedNodes = await Task.WhenAll(currentTasks) .ConfigureAwait(false); currentTasks.Clear(); // Add child nodes to pending nodes to execute the next level in parallel foreach (var node in completedNodes) { if (node is IParentExecutionNode p) { foreach (var childNode in p.GetChildNodes()) { pendingNodes.Enqueue(childNode); } } } } }
/// <summary> /// Initializes an instance of <see cref="ExecutionNode"/> with the specified values /// </summary> /// <param name="parent">The parent node, or null if this is the root node</param> /// <param name="graphType">The graph type of this node, unwrapped if it is a <see cref="NonNullGraphType"/>. Array nodes will be a <see cref="ListGraphType"/> instance.</param> /// <param name="field">The AST field of this node</param> /// <param name="fieldDefinition">The graph's field type of this node</param> /// <param name="indexInParentNode">For child array item nodes of a <see cref="ListGraphType"/>, the index of this array item within the field; otherwise, null</param> protected ExecutionNode(ExecutionNode parent, IGraphType graphType, Field field, FieldType fieldDefinition, int?indexInParentNode) { Parent = parent; GraphType = graphType; Field = field; FieldDefinition = fieldDefinition; IndexInParentNode = indexInParentNode; }
/// <summary> /// Initializes an instance of <see cref="ExecutionNode"/> with the specified values /// </summary> /// <param name="parent">The parent node, or <see langword="null"/> if this is the root node</param> /// <param name="graphType">The graph type of this node, unwrapped if it is a <see cref="NonNullGraphType"/>. Array nodes will be a <see cref="ListGraphType"/> instance.</param> /// <param name="field">The AST field of this node</param> /// <param name="fieldDefinition">The graph's field type of this node</param> /// <param name="indexInParentNode">For child array item nodes of a <see cref="ListGraphType"/>, the index of this array item within the field; otherwise, <see langword="null"/></param> protected ExecutionNode(ExecutionNode parent, IGraphType graphType, GraphQLField field, FieldType fieldDefinition, int?indexInParentNode) { Debug.Assert(field?.Name == fieldDefinition?.Name); // ? for RootExecutionNode Parent = parent; GraphType = graphType; Field = field !; FieldDefinition = fieldDefinition !; IndexInParentNode = indexInParentNode; }
public static ExecutionNode BuildExecutionNode(ExecutionNode parent, IGraphType graphType, Field field, FieldType fieldDefinition, int?indexInParentNode = null) { if (graphType is NonNullGraphType nonNullFieldType) { graphType = nonNullFieldType.ResolvedType; } return(graphType switch { ListGraphType _ => new ArrayExecutionNode(parent, graphType, field, fieldDefinition, indexInParentNode), IObjectGraphType _ => new ObjectExecutionNode(parent, graphType, field, fieldDefinition, indexInParentNode), IAbstractGraphType _ => new ObjectExecutionNode(parent, graphType, field, fieldDefinition, indexInParentNode), ScalarGraphType _ => new ValueExecutionNode(parent, graphType, field, fieldDefinition, indexInParentNode), _ => throw new InvalidOperationException($"Unexpected type: {graphType}") });
/// <summary> /// Builds an execution node with the specified parameters. /// </summary> protected ExecutionNode BuildSubscriptionExecutionNode(ExecutionNode parent, IGraphType graphType, Field field, FieldType fieldDefinition, int?indexInParentNode, object source) { if (graphType is NonNullGraphType nonNullFieldType) { graphType = nonNullFieldType.ResolvedType; } return(graphType switch { ListGraphType _ => new SubscriptionArrayExecutionNode(parent, graphType, field, fieldDefinition, indexInParentNode, source), IObjectGraphType _ => new SubscriptionObjectExecutionNode(parent, graphType, field, fieldDefinition, indexInParentNode, source), IAbstractGraphType _ => new SubscriptionObjectExecutionNode(parent, graphType, field, fieldDefinition, indexInParentNode, source), ScalarGraphType scalarGraphType => new SubscriptionValueExecutionNode(parent, scalarGraphType, field, fieldDefinition, indexInParentNode, source), _ => throw new InvalidOperationException($"Unexpected type: {graphType}") });
protected virtual void ValidateNodeResult(ExecutionContext context, ExecutionNode node) { var result = node.Result; IGraphType fieldType = node.FieldDefinition.ResolvedType; var objectType = fieldType as IObjectGraphType; if (fieldType is NonNullGraphType nonNullType) { if (result == null) { throw new ExecutionError("Cannot return null for non-null type." + $" Field: {node.Name}, Type: {nonNullType}."); } objectType = nonNullType.ResolvedType as IObjectGraphType; } if (result == null) { return; } if (fieldType is IAbstractGraphType abstractType) { objectType = abstractType.GetObjectType(result, context.Schema); if (objectType == null) { throw new ExecutionError( $"Abstract type {abstractType.Name} must resolve to an Object type at " + $"runtime for field {node.Parent.GraphType.Name}.{node.Name} " + $"with value '{result}', received 'null'."); } if (!abstractType.IsPossibleType(objectType)) { throw new ExecutionError($"Runtime Object type \"{objectType}\" is not a possible type for \"{abstractType}\"."); } } if (objectType?.IsTypeOf != null && !objectType.IsTypeOf(result)) { throw new ExecutionError($"\"{result}\" value of type \"{result.GetType()}\" is not allowed for \"{objectType.Name}\". Either change IsTypeOf method of \"{objectType.Name}\" to accept this value or return another value from your resolver."); } }
public static ExecutionNode BuildExecutionNode(ExecutionNode parent, IGraphType graphType, Field field, FieldType fieldDefinition, string[] path = null) { path ??= AppendPath(parent.Path, field.Name); if (graphType is NonNullGraphType nonNullFieldType) { graphType = nonNullFieldType.ResolvedType; } return(graphType switch { ListGraphType _ => new ArrayExecutionNode(parent, graphType, field, fieldDefinition, path), IObjectGraphType _ => new ObjectExecutionNode(parent, graphType, field, fieldDefinition, path), IAbstractGraphType _ => new ObjectExecutionNode(parent, graphType, field, fieldDefinition, path), ScalarGraphType _ => new ValueExecutionNode(parent, graphType, field, fieldDefinition, path), _ => throw new InvalidOperationException($"Unexpected type: {graphType}") });
public static void SetArrayItemNodes(ExecutionContext context, ArrayExecutionNode parent) { var listType = (ListGraphType)parent.GraphType; var itemType = listType.ResolvedType; if (itemType is NonNullGraphType nonNullGraphType) { itemType = nonNullGraphType.ResolvedType; } var data = parent.Result as IEnumerable; if (data == null) { var error = new ExecutionError("User error: expected an IEnumerable list though did not find one."); error.AddLocation(parent.Field, context.Document); throw error; } var index = 0; var arrayItems = new List <ExecutionNode>(); foreach (var d in data) { var path = AppendPath(parent.Path, (index++).ToString()); ExecutionNode node = BuildExecutionNode(parent, itemType, parent.Field, parent.FieldDefinition, path); node.Result = d; if (node is ObjectExecutionNode objectNode) { SetSubFieldNodes(context, objectNode); } arrayItems.Add(node); } parent.Items = arrayItems; }
protected virtual IObservable <ExecutionResult> ResolveEventStream(ExecutionContext context, ExecutionNode node) { context.CancellationToken.ThrowIfCancellationRequested(); var arguments = GetArgumentValues( context.Schema, node.FieldDefinition.Arguments, node.Field.Arguments, context.Variables); object source = (node.Parent != null) ? node.Parent.Result : context.RootValue; try { var resolveContext = new ResolveEventStreamContext { FieldName = node.Field.Name, FieldAst = node.Field, FieldDefinition = node.FieldDefinition, ReturnType = node.FieldDefinition.ResolvedType, ParentType = node.GraphType as IObjectGraphType, Arguments = arguments, Source = source, Schema = context.Schema, Document = context.Document, Fragments = context.Fragments, RootValue = context.RootValue, UserContext = context.UserContext, Operation = context.Operation, Variables = context.Variables, CancellationToken = context.CancellationToken, Metrics = context.Metrics, Errors = context.Errors, Path = node.Path }; var eventStreamField = node.FieldDefinition as EventStreamFieldType; if (eventStreamField?.Subscriber == null) { throw new InvalidOperationException($"Subscriber not set for field {node.Field.Name}"); } var subscription = eventStreamField.Subscriber.Subscribe(resolveContext); return(subscription .Select(value => { // Create new execution node return new ObjectExecutionNode(null, node.GraphType, node.Field, node.FieldDefinition, node.Path) { Source = value }; }) .SelectMany(async objectNode => { // Execute the whole execution tree and return the result await ExecuteNodeTreeAsync(context, objectNode) .ConfigureAwait(false); return new ExecutionResult { Data = new Dictionary <string, object> { { objectNode.Name, objectNode.ToValue() } } }; }) .Catch <ExecutionResult, Exception>(exception => Observable.Return( new ExecutionResult { Errors = new ExecutionErrors { new ExecutionError( $"Could not subscribe to field '{node.Field.Name}' in query '{context.Document.OriginalQuery}'", exception) { Path = node.Path } } }))); } catch (Exception ex) { GenerateError(context, node.Field, ex, node.Path); return(null); } }
/// <summary> /// Execute a single node /// </summary> /// <remarks> /// Builds child nodes, but does not execute them /// </remarks> protected virtual async Task <ExecutionNode> ExecuteNodeAsync(ExecutionContext context, ExecutionNode node) { context.CancellationToken.ThrowIfCancellationRequested(); if (node.IsResultSet) { return(node); } ResolveFieldContext resolveContext = null; try { var arguments = GetArgumentValues(context.Schema, node.FieldDefinition.Arguments, node.Field.Arguments, context.Variables); var subFields = SubFieldsFor(context, node.FieldDefinition.ResolvedType, node.Field); resolveContext = new ResolveFieldContext { FieldName = node.Field.Name, FieldAst = node.Field, FieldDefinition = node.FieldDefinition, ReturnType = node.FieldDefinition.ResolvedType, ParentType = node.GetParentType(context.Schema), Arguments = arguments, Source = node.Source, Schema = context.Schema, Document = context.Document, Fragments = context.Fragments, RootValue = context.RootValue, UserContext = context.UserContext, Operation = context.Operation, Variables = context.Variables, CancellationToken = context.CancellationToken, Metrics = context.Metrics, Errors = context.Errors, Path = node.Path, SubFields = subFields }; var resolver = node.FieldDefinition.Resolver ?? new NameFieldResolver(); var result = resolver.Resolve(resolveContext); if (result is Task task) { await task.ConfigureAwait(false); result = task.GetResult(); } node.Result = result; ValidateNodeResult(context, node); // Build child nodes if (node.Result != null) { if (node is ObjectExecutionNode objectNode) { SetSubFieldNodes(context, objectNode); } else if (node is ArrayExecutionNode arrayNode) { SetArrayItemNodes(context, arrayNode); } } } catch (ExecutionError error) { error.AddLocation(node.Field, context.Document); error.Path = node.Path; context.Errors.Add(error); node.Result = null; } catch (Exception ex) { if (context.ThrowOnUnhandledException) { throw; } UnhandledExceptionContext exceptionContext = null; if (context.UnhandledExceptionDelegate != null) { exceptionContext = new UnhandledExceptionContext(context, resolveContext, ex); context.UnhandledExceptionDelegate(exceptionContext); ex = exceptionContext.Exception; } var error = new ExecutionError(exceptionContext?.ErrorMessage ?? $"Error trying to resolve {node.Name}.", ex); error.AddLocation(node.Field, context.Document); error.Path = node.Path; context.Errors.Add(error); node.Result = null; } return(node); }
public Job CreateChildJob(ExecutionContext context, ExecutionNode node) { return(new Job(_counter, context, node)); }
/// <summary> /// Initializes an instance of <see cref="ValueExecutionNode"/> with the specified values. /// </summary> public ValueExecutionNode(ExecutionNode parent, ScalarGraphType graphType, GraphQLField field, FieldType fieldDefinition, int?indexInParentNode) : base(parent, graphType, field, fieldDefinition, indexInParentNode) { }
/// <summary> /// Initializes an <see cref="SubscriptionArrayExecutionNode"/> instance with the specified values. /// </summary> public SubscriptionArrayExecutionNode(ExecutionNode parent, IGraphType graphType, GraphQLField field, FieldType fieldDefinition, int?indexInParentNode, object source) : base(parent, graphType, field, fieldDefinition, indexInParentNode) { Source = source; }
protected virtual async Task <IObservable <object> > ResolveEventStreamAsync(ExecutionContext context, ExecutionNode node) { context.CancellationToken.ThrowIfCancellationRequested(); var arguments = GetArgumentValues( context.Schema, node.FieldDefinition.Arguments, node.Field.Arguments, context.Variables); object source = (node.Parent != null) ? node.Parent.Result : context.RootValue; try { var resolveContext = new ResolveEventStreamContext { FieldName = node.Field.Name, FieldAst = node.Field, FieldDefinition = node.FieldDefinition, ReturnType = node.FieldDefinition.ResolvedType, ParentType = node.GetParentType(context.Schema), Arguments = arguments, Source = source, Schema = context.Schema, Document = context.Document, Fragments = context.Fragments, RootValue = context.RootValue, UserContext = context.UserContext, Operation = context.Operation, Variables = context.Variables, CancellationToken = context.CancellationToken, Metrics = context.Metrics, Errors = context.Errors, Path = node.Path }; var eventStreamField = node.FieldDefinition as EventStreamFieldType; IObservable <object> subscription; if (eventStreamField?.Subscriber != null) { subscription = eventStreamField.Subscriber.Subscribe(resolveContext); } else if (eventStreamField?.AsyncSubscriber != null) { subscription = await eventStreamField.AsyncSubscriber.SubscribeAsync(resolveContext); } else { throw new InvalidOperationException($"Subscriber not set for field {node.Field.Name}"); } return(subscription); } catch (Exception ex) { var message = $"Error trying to resolve {node.Field.Name}."; var error = GenerateError(context, message, node.Field, node.Path, ex); context.Errors.Add(error); return(null); } }
public SubscriptionValueExecutionNode(ExecutionNode parent, ScalarGraphType graphType, Field field, FieldType fieldDefinition, int?indexInParentNode, object source) : base(parent, graphType, field, fieldDefinition, indexInParentNode) { Source = source; }
/// <summary> /// Initializes an instance of <see cref="NullExecutionNode"/> with the specified values. /// </summary> public NullExecutionNode(ExecutionNode parent, IGraphType graphType, Field field, FieldType fieldDefinition, int?indexInParentNode) : base(parent, graphType, field, fieldDefinition, indexInParentNode) { Result = null; }
public Job(ExecutionContext context, ExecutionNode node) : this(new JobsCounter(), context, node) { }
protected virtual async Task <IObservable <ExecutionResult> > ResolveEventStreamAsync(ExecutionContext context, ExecutionNode node) { context.CancellationToken.ThrowIfCancellationRequested(); var arguments = ExecutionHelper.GetArgumentValues( node.FieldDefinition.Arguments, node.Field.Arguments, context.Variables); object source = (node.Parent != null) ? node.Parent.Result : context.RootValue; try { var resolveContext = new ResolveEventStreamContext { FieldAst = node.Field, FieldDefinition = node.FieldDefinition, ParentType = node.GetParentType(context.Schema), Arguments = arguments, Source = source, Schema = context.Schema, Document = context.Document, RootValue = context.RootValue, UserContext = context.UserContext, Operation = context.Operation, Variables = context.Variables, CancellationToken = context.CancellationToken, Metrics = context.Metrics, Errors = context.Errors, Path = node.Path, RequestServices = context.RequestServices, }; var eventStreamField = node.FieldDefinition as EventStreamFieldType; IObservable <object> subscription; if (eventStreamField?.Subscriber != null) { subscription = eventStreamField.Subscriber.Subscribe(resolveContext); } else if (eventStreamField?.AsyncSubscriber != null) { subscription = await eventStreamField.AsyncSubscriber.SubscribeAsync(resolveContext).ConfigureAwait(false); } else { throw new InvalidOperationException($"Subscriber not set for field '{node.Field.Name}'."); } return(subscription .Select(value => BuildSubscriptionExecutionNode(node.Parent, node.GraphType, node.Field, node.FieldDefinition, node.IndexInParentNode, value)) .SelectMany(async executionNode => { if (context.Listeners != null) { foreach (var listener in context.Listeners) { await listener.BeforeExecutionAsync(context) .ConfigureAwait(false); } } // Execute the whole execution tree and return the result await ExecuteNodeTreeAsync(context, executionNode).ConfigureAwait(false); if (context.Listeners != null) { foreach (var listener in context.Listeners) { await listener.AfterExecutionAsync(context) .ConfigureAwait(false); } } // Set the execution node's value to null if necessary // Note: assumes that the subscription field is nullable, regardless of how it was defined // See https://github.com/graphql-dotnet/graphql-dotnet/pull/2240#discussion_r570631402 //TODO: check if a non-null subscription field is allowed per the spec //TODO: check if errors should be returned along with the data executionNode.PropagateNull(); // Return the result return new ExecutionResult { Data = new RootExecutionNode(null, null) { SubFields = new ExecutionNode[] { executionNode, } }, }.With(context); }) .Catch <ExecutionResult, Exception>(exception => Observable.Return( new ExecutionResult { Errors = new ExecutionErrors { GenerateError( context, $"Could not subscribe to field '{node.Field.Name}' in query '{context.Document.OriginalQuery}'.", node.Field, node.ResponsePath, exception) } }.With(context)))); } catch (Exception ex) { var message = $"Error trying to resolve field '{node.Field.Name}'."; var error = GenerateError(context, message, node.Field, node.ResponsePath, ex); context.Errors.Add(error); return(null); } }
/// <inheritdoc cref="ExecuteNodeTreeAsync(ExecutionContext, ObjectExecutionNode)"/> protected async Task ExecuteNodeTreeAsync(ExecutionContext context, ExecutionNode rootNode) { var pendingNodes = new Queue <ExecutionNode>(); pendingNodes.Enqueue(rootNode); var pendingDataLoaders = new Queue <ExecutionNode>(); var currentTasks = new List <Task>(); var currentNodes = new List <ExecutionNode>(); while (pendingNodes.Count > 0 || pendingDataLoaders.Count > 0 || currentTasks.Count > 0) { while (pendingNodes.Count > 0 || currentTasks.Count > 0) { // Start executing pending nodes, while limiting the maximum number of parallel executed nodes to the set limit while ((context.MaxParallelExecutionCount == null || currentTasks.Count < context.MaxParallelExecutionCount) && pendingNodes.Count > 0) { context.CancellationToken.ThrowIfCancellationRequested(); var pendingNode = pendingNodes.Dequeue(); var pendingNodeTask = ExecuteNodeAsync(context, pendingNode); if (pendingNodeTask.IsCompleted) { // Throw any caught exceptions await pendingNodeTask; // Node completed synchronously, so no need to add it to the list of currently executing nodes // instead add any child nodes to the pendingNodes queue directly here if (pendingNode.Result is IDataLoaderResult) { pendingDataLoaders.Enqueue(pendingNode); } else if (pendingNode is IParentExecutionNode parentExecutionNode) { foreach (var childNode in parentExecutionNode.GetChildNodes()) { pendingNodes.Enqueue(childNode); } } } else { // Node is actually asynchronous, so add it to the list of current tasks being executed in parallel currentTasks.Add(pendingNodeTask); currentNodes.Add(pendingNode); } } #pragma warning disable CS0612 // Type or member is obsolete await OnBeforeExecutionStepAwaitedAsync(context) #pragma warning restore CS0612 // Type or member is obsolete .ConfigureAwait(false); // Await tasks for this execution step await Task.WhenAll(currentTasks) .ConfigureAwait(false); // Add child nodes to pending nodes to execute the next level in parallel foreach (var node in currentNodes) { if (node.Result is IDataLoaderResult) { pendingDataLoaders.Enqueue(node); } else if (node is IParentExecutionNode p) { foreach (var childNode in p.GetChildNodes()) { pendingNodes.Enqueue(childNode); } } } currentTasks.Clear(); currentNodes.Clear(); } //run pending data loaders while (pendingDataLoaders.Count > 0) { var dataLoaderNode = pendingDataLoaders.Dequeue(); currentTasks.Add(CompleteDataLoaderNodeAsync(context, dataLoaderNode)); currentNodes.Add(dataLoaderNode); } } }
protected virtual async Task <IObservable <ExecutionResult> > ResolveEventStreamAsync(ExecutionContext context, ExecutionNode node) { context.CancellationToken.ThrowIfCancellationRequested(); var arguments = GetArgumentValues( context.Schema, node.FieldDefinition.Arguments, node.Field.Arguments, context.Variables); object source = (node.Parent != null) ? node.Parent.Result : context.RootValue; try { var resolveContext = new ResolveEventStreamContext { FieldName = node.Field.Name, FieldAst = node.Field, FieldDefinition = node.FieldDefinition, ReturnType = node.FieldDefinition.ResolvedType, ParentType = node.GetParentType(context.Schema), Arguments = arguments, Source = source, Schema = context.Schema, Document = context.Document, Fragments = context.Fragments, RootValue = context.RootValue, UserContext = context.UserContext, Operation = context.Operation, Variables = context.Variables, CancellationToken = context.CancellationToken, Metrics = context.Metrics, Errors = context.Errors, Path = node.Path }; var eventStreamField = node.FieldDefinition as EventStreamFieldType; IObservable <object> subscription; if (eventStreamField?.Subscriber != null) { subscription = eventStreamField.Subscriber.Subscribe(resolveContext); } else if (eventStreamField?.AsyncSubscriber != null) { subscription = await eventStreamField.AsyncSubscriber.SubscribeAsync(resolveContext).ConfigureAwait(false); } else { throw new InvalidOperationException($"Subscriber not set for field {node.Field.Name}"); } return(subscription .Select(value => { var executionNode = BuildExecutionNode(node.Parent, node.GraphType, node.Field, node.FieldDefinition, node.IndexInParentNode); executionNode.Source = value; return executionNode; }) .SelectMany(async executionNode => { if (context.Listeners != null) { foreach (var listener in context.Listeners) { await listener.BeforeExecutionAsync(context) .ConfigureAwait(false); } } // Execute the whole execution tree and return the result await ExecuteNodeTreeAsync(context, executionNode).ConfigureAwait(false); if (context.Listeners != null) { foreach (var listener in context.Listeners) { await listener.AfterExecutionAsync(context) .ConfigureAwait(false); } } return new ExecutionResult { Data = new Dictionary <string, object> { { executionNode.Name, executionNode.ToValue() } } }.With(context); }) .Catch <ExecutionResult, Exception>(exception => Observable.Return( new ExecutionResult { Errors = new ExecutionErrors { GenerateError( context, $"Could not subscribe to field '{node.Field.Name}' in query '{context.Document.OriginalQuery}'", node.Field, node.Path, exception) } }.With(context)))); } catch (Exception ex) { var message = $"Error trying to resolve {node.Field.Name}."; var error = GenerateError(context, message, node.Field, node.Path, ex); context.Errors.Add(error); return(null); } }
public ArrayExecutionNode(ExecutionNode parent, IGraphType graphType, Field field, FieldType fieldDefinition, int?indexInParentNode) : base(parent, graphType, field, fieldDefinition, indexInParentNode) { }
/// <inheritdoc/> public abstract Task ExecuteNodeTreeAsync(ExecutionContext context, ExecutionNode rootNode);
/// <summary> /// Executes document nodes in parallel. Field resolvers must be designed for multi-threaded use. /// Nodes that return a <see cref="IDataLoaderResult"/> will execute once all other pending nodes /// have been completed. /// </summary> public override async Task ExecuteNodeTreeAsync(ExecutionContext context, ExecutionNode rootNode) { var pendingNodes = System.Threading.Interlocked.Exchange(ref _reusablePendingNodes, null) ?? new Queue <ExecutionNode>(); pendingNodes.Enqueue(rootNode); var pendingDataLoaders = System.Threading.Interlocked.Exchange(ref _reusablePendingDataLoaders, null) ?? new Queue <ExecutionNode>(); var currentTasks = System.Threading.Interlocked.Exchange(ref _reusableCurrentTasks, null) ?? new List <Task>(); var currentNodes = System.Threading.Interlocked.Exchange(ref _reusableCurrentNodes, null) ?? new List <ExecutionNode>(); try { while (pendingNodes.Count > 0 || pendingDataLoaders.Count > 0 || currentTasks.Count > 0) { while (pendingNodes.Count > 0 || currentTasks.Count > 0) { // Start executing pending nodes, while limiting the maximum number of parallel executed nodes to the set limit while ((context.MaxParallelExecutionCount == null || currentTasks.Count < context.MaxParallelExecutionCount) && pendingNodes.Count > 0) { context.CancellationToken.ThrowIfCancellationRequested(); var pendingNode = pendingNodes.Dequeue(); var pendingNodeTask = ExecuteNodeAsync(context, pendingNode); if (pendingNodeTask.IsCompleted) { // Throw any caught exceptions await pendingNodeTask.ConfigureAwait(false); // Node completed synchronously, so no need to add it to the list of currently executing nodes // instead add any child nodes to the pendingNodes queue directly here if (pendingNode.Result is IDataLoaderResult) { pendingDataLoaders.Enqueue(pendingNode); } else if (pendingNode is IParentExecutionNode parentExecutionNode) { parentExecutionNode.ApplyToChildren((node, state) => state.Enqueue(node), pendingNodes); } } else { // Node is actually asynchronous, so add it to the list of current tasks being executed in parallel currentTasks.Add(pendingNodeTask); currentNodes.Add(pendingNode); } } // Await tasks for this execution step await Task.WhenAll(currentTasks) .ConfigureAwait(false); // Add child nodes to pending nodes to execute the next level in parallel foreach (var node in currentNodes) { if (node.Result is IDataLoaderResult) { pendingDataLoaders.Enqueue(node); } else if (node is IParentExecutionNode p) { p.ApplyToChildren((node, state) => state.Enqueue(node), pendingNodes); } } currentTasks.Clear(); currentNodes.Clear(); } //run pending data loaders while (pendingDataLoaders.Count > 0) { var dataLoaderNode = pendingDataLoaders.Dequeue(); currentTasks.Add(CompleteDataLoaderNodeAsync(context, dataLoaderNode)); currentNodes.Add(dataLoaderNode); } } } catch (Exception original) { if (currentTasks.Count > 0) { try { await Task.WhenAll(currentTasks).ConfigureAwait(false); } catch (Exception awaited) { if (original.Data?.IsReadOnly == false) { original.Data["GRAPHQL_ALL_TASKS_AWAITED_EXCEPTION"] = awaited; } } } throw; } finally { pendingNodes.Clear(); pendingDataLoaders.Clear(); currentTasks.Clear(); currentNodes.Clear(); System.Threading.Interlocked.CompareExchange(ref _reusablePendingNodes, pendingNodes, null); System.Threading.Interlocked.CompareExchange(ref _reusablePendingDataLoaders, pendingDataLoaders, null); System.Threading.Interlocked.CompareExchange(ref _reusableCurrentTasks, currentTasks, null); System.Threading.Interlocked.CompareExchange(ref _reusableCurrentNodes, currentNodes, null); } }
/// <summary> /// Executes document nodes serially. Nodes that return a <see cref="IDataLoaderResult"/> will /// execute once all other pending nodes have been completed. /// </summary> public override async Task ExecuteNodeTreeAsync(ExecutionContext context, ExecutionNode rootNode) { // Use a stack to track all nodes in the tree that need to be executed var nodes = System.Threading.Interlocked.Exchange(ref _reusableNodes, null) ?? new Stack <ExecutionNode>(); nodes.Push(rootNode); var dataLoaderNodes = System.Threading.Interlocked.Exchange(ref _reusableDataLoaderNodes, null) ?? new Queue <ExecutionNode>(); var addlNodes = System.Threading.Interlocked.Exchange(ref _reusableAddlNodes, null) ?? new Stack <ExecutionNode>(); try { // Process each node on the stack one by one while (nodes.Count > 0 || dataLoaderNodes.Count > 0) { while (nodes.Count > 0) { var node = nodes.Pop(); await ExecuteNodeAsync(context, node).ConfigureAwait(false); // Push any child nodes on top of the stack if (node.Result is IDataLoaderResult) { dataLoaderNodes.Enqueue(node); } else if (node is IParentExecutionNode parentNode) { // Add in reverse order so fields are executed in the correct order parentNode.ApplyToChildren((node, state) => state.Push(node), nodes, reverse: true); } } while (dataLoaderNodes.Count > 0) { var node = dataLoaderNodes.Dequeue(); await CompleteDataLoaderNodeAsync(context, node).ConfigureAwait(false); // Push any child nodes on top of the stack if (node.Result is IDataLoaderResult) { dataLoaderNodes.Enqueue(node); } else if (node is IParentExecutionNode parentNode) { // Do not reverse the order of the nodes here parentNode.ApplyToChildren((node, state) => state.Push(node), addlNodes, reverse: false); } } // Reverse order of queued nodes from data loader nodes so they are executed in the correct order while (addlNodes.Count > 0) { nodes.Push(addlNodes.Pop()); } } } finally { nodes.Clear(); dataLoaderNodes.Clear(); addlNodes.Clear(); System.Threading.Interlocked.CompareExchange(ref _reusableNodes, nodes, null); System.Threading.Interlocked.CompareExchange(ref _reusableDataLoaderNodes, dataLoaderNodes, null); System.Threading.Interlocked.CompareExchange(ref _reusableAddlNodes, addlNodes, null); } }