/// <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}");
            }
        }
Exemple #5
0
 private Job(JobsCounter counter, ExecutionContext context, ExecutionNode node)
 {
     _counter = counter;
     _counter.Increment();
     Context = context;
     Node    = node;
 }
Exemple #6
0
        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);
                        }
                    }
                }
            }
        }
Exemple #7
0
 /// <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;
 }
Exemple #8
0
        /// <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;
        }
Exemple #14
0
        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);
        }
Exemple #16
0
 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)
 {
 }
Exemple #18
0
 /// <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;
 }
Exemple #19
0
        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;
 }
Exemple #22
0
 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);
            }
        }
Exemple #26
0
 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);
Exemple #28
0
        /// <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);
            }
        }