public async Task InvokeAsync(GraphFieldExecutionContext context, GraphMiddlewareInvocationDelegate <GraphFieldExecutionContext> next, CancellationToken cancelToken) { _testService.BeforeNext(nameof(TestMiddleware2)); await next(context, cancelToken); _testService.AfterNext(nameof(TestMiddleware2)); }
/// <summary> /// Called by the runtime to invoke the middleware logic. In most cases, the middleware component should return the task /// generated by calling the next delegate in the request chain with the provided context. /// </summary> /// <param name="context">The invocation request governing data in this pipeline run.</param> /// <param name="next">The next delegate in the chain to be invoked.</param> /// <param name="cancelToken">The cancel token.</param> /// <returns>Task.</returns> public async Task InvokeAsync(GraphFieldExecutionContext context, GraphMiddlewareInvocationDelegate <GraphFieldExecutionContext> next, CancellationToken cancelToken) { FieldAuthorizationResult result = FieldAuthorizationResult.Default(); if (context.IsValid) { // execute the authorization pipeline var authRequest = new GraphFieldAuthorizationRequest(context.Request); var authContext = new GraphFieldAuthorizationContext(context, authRequest); await _authPipeline.InvokeAsync(authContext, cancelToken).ConfigureAwait(false); result = authContext.Result ?? FieldAuthorizationResult.Default(); // by default, deny any stati not explicitly declared as "successful" by this component. if (!result.Status.IsAuthorized()) { context.Messages.Critical( $"Access Denied to field {context.Field.Route.Path}", Constants.ErrorCodes.ACCESS_DENIED, context.Request.Origin); } } if (!result.Status.IsAuthorized()) { context.ResolvedSourceItems.AddRange(context.Request.DataSource.Items); context.ResolvedSourceItems.ForEach(x => x.Fail()); } await next(context, cancelToken).ConfigureAwait(false); }
/// <summary> /// Invoke the action item as an asyncronous operation. /// </summary> /// <param name="context">The context.</param> /// <param name="next">The next.</param> /// <param name="cancelToken">The cancel token.</param> /// <returns>Task.</returns> public async Task InvokeAsync(GraphFieldExecutionContext context, GraphMiddlewareInvocationDelegate <GraphFieldExecutionContext> next, CancellationToken cancelToken = default) { // create a set of validation contexts for every incoming source graph item // to capture and validate every item regardless of it being successfully resolved or failed var validationContexts = context.Request.DataSource.Items.Select( x => new FieldValidationContext(_schema, x, context.Messages)); // begin profiling of this single field of data context.Metrics?.BeginFieldResolution(context); bool fieldShouldBeCanceled = false; if (context.IsValid) { // build a collection of invokable parameters from the supplied context var executionArguments = context .InvocationContext .Arguments .Merge(context.VariableData) .WithSourceData(context.Request.DataSource.Value); // resolve the field var resolutionContext = new FieldResolutionContext(context, context.Request, executionArguments); context.Logger?.FieldResolutionStarted(resolutionContext); var task = context.Field?.Resolver?.Resolve(resolutionContext, cancelToken); await task.ConfigureAwait(false); context.Messages.AddRange(resolutionContext.Messages); this.AssignResults(context, resolutionContext); fieldShouldBeCanceled = resolutionContext.IsCancelled; context.Logger?.FieldResolutionCompleted(resolutionContext); } if (fieldShouldBeCanceled) { context.Cancel(); context.Request.DataSource.Items.ForEach(x => x.Cancel()); } // validate the resolution of the field in whatever manner that means for its current state var completionProcessor = new FieldCompletionRuleProcessor(); completionProcessor.Execute(validationContexts); // end profiling of this single field of data context.Metrics?.EndFieldResolution(context); await next(context, cancelToken).ConfigureAwait(false); // validate the final result after all downstream middleware execute // in the standard pipeline this generally means all child fields have resolved var validationProcessor = new FieldValidationRuleProcessor(); validationProcessor.Execute(validationContexts); }
/// <summary> /// Called by the runtime to invoke the middleware logic. In most cases, the middleware component should return the task /// generated by calling the next delegate in the request chain with the provided context. /// </summary> /// <param name="fieldRequest">The invocation request governing data in this pipeline run.</param> /// <param name="next">The next delegate in the chain to be invoked.</param> /// <param name="cancelToken">The cancel token.</param> /// <returns>Task.</returns> public Task InvokeAsync( GraphFieldExecutionContext fieldRequest, GraphMiddlewareInvocationDelegate <GraphFieldExecutionContext> next, CancellationToken cancelToken = default) { Counter++; return(next(fieldRequest, cancelToken)); }
/// <summary> /// Marks the end of a single resolver attempting to generate data according to its own specifications. /// </summary> /// <param name="context">The context outlining the resolution that is taking place.</param> public virtual void EndFieldResolution(GraphFieldExecutionContext context) { var endTime = _watch.ElapsedTicks; if (_resolverEntries.TryGetValue(context, out var entry)) { entry.EndOffsetTicks = endTime; } }
/// <summary> /// Invokes this middleware component allowing it to perform its work against the supplied context. /// </summary> /// <param name="context">The context containing the request passed through the pipeline.</param> /// <param name="next">The delegate pointing to the next piece of middleware to be invoked.</param> /// <param name="cancelToken">The cancel token.</param> /// <returns>Task.</returns> public async Task InvokeAsync(GraphFieldExecutionContext context, GraphMiddlewareInvocationDelegate <GraphFieldExecutionContext> next, CancellationToken cancelToken) { if (context.IsValid && context.Result != null && !context.IsCancelled) { await this.ProcessDownStreamFieldContexts(context, cancelToken).ConfigureAwait(false); } await next(context, cancelToken).ConfigureAwait(false); }
public Task InvokeAsync(GraphFieldExecutionContext context, GraphMiddlewareInvocationDelegate <GraphFieldExecutionContext> next, CancellationToken cancelToken) { if (context.Field.Name.Contains("property1", System.StringComparison.OrdinalIgnoreCase)) { throw new GraphExecutionException("Forced Exception for testing"); } return(next(context, cancelToken)); }
/// <summary> /// Marks the start of a single resolver attempting to generate data according to its own specifications. /// </summary> /// <param name="context">The context outlining the resolution that is taking place.</param> public virtual void BeginFieldResolution(GraphFieldExecutionContext context) { var startTime = _watch.ElapsedTicks; var entry = new ApolloMetricsEntry() { StartOffsetTicks = startTime, }; _resolverEntries.TryAdd(context, entry); }
/// <summary> /// Invokes this middleware component allowing it to perform its work against the supplied context. /// </summary> /// <param name="context">The context containing the request passed through the pipeline.</param> /// <param name="next">The delegate pointing to the next piece of middleware to be invoked.</param> /// <param name="cancelToken">The cancel token.</param> /// <returns>Task.</returns> public Task InvokeAsync(GraphFieldExecutionContext context, GraphMiddlewareInvocationDelegate <GraphFieldExecutionContext> next, CancellationToken cancelToken) { // ensure that the data items on teh request match the field they are being executed against var field = context.Field; var expectedFieldGraphType = _schema.KnownTypes.FindGraphType(field); var dataSource = context.Request.DataSource; // ensure the source being supplied // matches the expected source of the field being resolved if (context.InvocationContext.ExpectedSourceType != null) { var expectedSourceType = context.InvocationContext.ExpectedSourceType; var actualSourceType = GraphValidation.EliminateWrappersFromCoreType(dataSource.Value.GetType()); if (expectedSourceType != actualSourceType) { var expectedSourceGraphType = _schema.KnownTypes.FindGraphType(expectedSourceType, TypeKind.OBJECT); var analysis = _schema.KnownTypes.AnalyzeRuntimeConcreteType(expectedSourceGraphType, actualSourceType); if (!analysis.ExactMatchFound) { throw new GraphExecutionException( $"Operation failed. The field execution context for '{field.Route.Path}' was passed " + $"a source item of type '{dataSource.Value.GetType().FriendlyName()}' which could not be coerced " + $"to '{context.InvocationContext.ExpectedSourceType}' as requested by the target graph type '{expectedFieldGraphType.Name}'."); } if (context.Field.Mode == FieldResolutionMode.Batch && !(dataSource.GetType() is IEnumerable)) { throw new GraphExecutionException( $"Operation failed. The field execution context for '{field.Route.Path}' was executed in batch mode " + $"but was not passed an {nameof(IEnumerable)} for its source data."); } } } if (dataSource.Items.Count == 0) { var expected = context.InvocationContext.Field.Mode == FieldResolutionMode.PerSourceItem ? "1" : "at least 1"; throw new GraphExecutionException( $"Operation failed. The field execution context for '{field.Route.Path}' was passed " + $"0 items but expected {expected}. (Field Mode: {field.Mode.ToString()})"); } if (context.InvocationContext.Field.Mode == FieldResolutionMode.PerSourceItem && dataSource.Items.Count != 1) { throw new GraphExecutionException( $"Operation failed. The field execution context for '{field.Route.Path}' was passed " + $"{dataSource.Items.Count} items to resolve but expected 1. (Field Mode: {field.Mode.ToString()})"); } return(next(context, cancelToken)); }
/// <summary> /// Invokes this middleware component allowing it to perform its work against the supplied context. /// </summary> /// <param name="context">The context containing the request passed through the pipeline.</param> /// <param name="next">The delegate pointing to the next piece of middleware to be invoked.</param> /// <param name="cancelToken">The cancel token.</param> /// <returns>Task.</returns> public Task InvokeAsync(GraphFieldExecutionContext context, GraphMiddlewareInvocationDelegate <GraphFieldExecutionContext> next, CancellationToken cancelToken) { // ensure that the data items on teh request match the field they are being executed against var field = context.Field; var dataSource = context.Request.DataSource; if (context.InvocationContext.ExpectedSourceType != null) { var expectedType = context.InvocationContext.ExpectedSourceType; if (context.Field.Mode == FieldResolutionMode.Batch) { expectedType = typeof(List <>).MakeGenericType(expectedType); } if (dataSource.Value.GetType() != expectedType) { throw new GraphExecutionException( $"Operation failed. The field execution context for '{field.Route.Path}' was passed " + $"a source item of type '{dataSource.Value.GetType().FriendlyName()}' but expected '{context.InvocationContext.ExpectedSourceType}'."); } } if (dataSource.Items.Count == 0) { var expected = context.InvocationContext.Field.Mode == FieldResolutionMode.PerSourceItem ? "1" : "at least 1"; throw new GraphExecutionException( $"Operation failed. The field execution context for '{field.Route.Path}' was passed " + $"0 items but expected {expected}. (Field Mode: {field.Mode.ToString()})"); } if (context.InvocationContext.Field.Mode == FieldResolutionMode.PerSourceItem && dataSource.Items.Count != 1) { throw new GraphExecutionException( $"Operation failed. The field execution context for '{field.Route.Path}' was passed " + $"{dataSource.Items.Count} items to resolve but expected 1. (Field Mode: {field.Mode.ToString()})"); } return(next(context, cancelToken)); }
/// <summary> /// Assigns the results of resolving the field to the items on the execution context. /// </summary> /// <param name="executionContext">The execution context.</param> /// <param name="resolutionContext">The resolution context.</param> private void AssignResults(GraphFieldExecutionContext executionContext, FieldResolutionContext resolutionContext) { // transfer the result to the execution context // then deteremine what (if any) data items can be updated from its value executionContext.Result = resolutionContext.Result; if (executionContext.Field.Mode == FieldResolutionMode.PerSourceItem) { if (executionContext.Request.DataSource.Items.Count == 1) { var item = executionContext.Request.DataSource.Items[0]; executionContext.ResolvedSourceItems.Add(item); item.AssignResult(resolutionContext.Result); return; } throw new GraphExecutionException( $"When attempting to resolve the field '{executionContext.Field.Route.Path}' an unexpected error occured and the request was teriminated.", executionContext.Request.Origin, new InvalidOperationException( $"The field '{executionContext.Field.Route.Parent}' has a resolution mode of '{nameof(FieldResolutionMode.PerSourceItem)}' " + $"but the execution context contains {executionContext.Request.DataSource.Items.Count} source items. The runtime is unable to determine which " + "item to assign the resultant value to.")); } else if (executionContext.Field.Mode == FieldResolutionMode.Batch) { var batchProcessor = new BatchResultProcessor( executionContext.Field, executionContext.Request.DataSource.Items, executionContext.Request.Origin); var itemsWithAssignedData = batchProcessor.Resolve(executionContext.Result); executionContext.ResolvedSourceItems.AddRange(itemsWithAssignedData); executionContext.Messages.AddRange(batchProcessor.Messages); return; } throw new ArgumentOutOfRangeException( nameof(executionContext.Field.Mode), $"The execution mode for field '{executionContext.Field.Route.Path}' cannot be resolved " + $"by {nameof(InvokeFieldResolverMiddleware<TSchema>)}. (Mode: {executionContext.Field.Mode.ToString()})"); }
/// <summary> /// Inspects both the childcontext and the invoked task to determine /// if any messages were found or any unhandled exceptions were thrown then /// captures the results into the parent context for upstream processing or /// reporting. /// </summary> /// <param name="parentContext">The parent context that invoked the child.</param> /// <param name="childContext">The child context that was invoked.</param> /// <param name="childExecutionTask">The actual task representing the child /// pipeline.</param> private void CaptureChildFieldExecutionResults( GraphFieldExecutionContext parentContext, GraphFieldExecutionContext childContext, Task childExecutionTask) { // capture any messages that the child context may have internally reported parentContext.Messages.AddRange(childContext.Messages); // inspect the task for faulting and capture any exceptions as critical errors if (childExecutionTask.IsFaulted) { var exception = childExecutionTask.UnwrapException(); exception = exception ?? new Exception( "Unknown Error. The child field execution pipeline indicated a fault but did not " + "provide a reason for the failure."); parentContext.Messages.Critical( $"Processing field '{childContext.Field.Name}' of '{parentContext.Field.Route}' resulted in a critical failure. See exception for details.", Constants.ErrorCodes.EXECUTION_ERROR, childContext.InvocationContext.Origin, exception); } }
/// <summary> /// For any resolved, non-leaf items assigned to the result, pass each through the resolution pipeline /// and await their individual results. /// </summary> /// <param name="context">The "parent" context supplying data for downstream reslts.</param> /// <param name="cancelToken">The cancel token.</param> /// <returns>Task.</returns> private async Task ProcessDownStreamFieldContexts(GraphFieldExecutionContext context, CancellationToken cancelToken) { if (context.InvocationContext.ChildContexts.Count == 0) { return; } // items resolved on this active context become the source for any downstream fields // --- // can never extract child fields from a null value (even if its valid for the item) // or one that isnt read for it List <GraphDataItem> allSourceItems = context .ResolvedSourceItems .SelectMany(x => x.FlattenListItemTree()) .Where(x => x.ResultData != null && x.Status == FieldItemResolutionStatus.NeedsChildResolution) .ToList(); if (allSourceItems.Count == 0) { return; } // find a reference to the graph type for the field var graphType = _schema.KnownTypes.FindGraphType(context.Field.TypeExpression.TypeName); // theoretically it can't not be found, but you never know if (graphType == null) { var msg = $"Internal Server Error. When processing the results of '{context.Field.Route.Path}' no graph type on the target schema " + $"could be found for the type name '{context.Field.TypeExpression.TypeName}'. " + $"Unable to process the {allSourceItems.Count} item(s) generated."; context.Messages.Add( GraphMessageSeverity.Critical, Constants.ErrorCodes.EXECUTION_ERROR, msg, context.Request.Origin); context.Cancel(); return; } var pipelines = new List <Task>(); // Step 0 // ----------------------------------------------------------------------- // create a lookup of source items by concrete type known to the schema, for easy seperation to the individual // downstream child contexts var sourceItemLookup = this.MapExpectedConcreteTypeFromSourceItem(allSourceItems, graphType); foreach (var childInvocationContext in context.InvocationContext.ChildContexts) { // Step 1 // ---------------------------- // figure out which child items need to be processed through it IEnumerable <GraphDataItem> sourceItemsToInclude; if (childInvocationContext.ExpectedSourceType == null) { sourceItemsToInclude = allSourceItems; } else { // if no children match the required type of the children present, then skip it // this can happen quite often in the case of a union or an interface where multiple invocation contexts // are added to a plan for the same child field in case a parent returns a member of the union or an // implementer of the interface if (!sourceItemLookup.ContainsKey(childInvocationContext.ExpectedSourceType)) { continue; } sourceItemsToInclude = sourceItemLookup[childInvocationContext.ExpectedSourceType]; } // Step 1B // For any source items replace any virtual objects with defaults found // on the context for the field in question if (context.DefaultFieldSources.TryRetrieveSource(childInvocationContext.Field, out var defaultSource)) { sourceItemsToInclude = sourceItemsToInclude.Select((currentValue) => { if (currentValue.ResultData is VirtualResolvedObject) { currentValue.AssignResult(defaultSource); } return(currentValue); }); } // Step 2 // ---------------------------- // when the invocation is as a batch, create one execution context for all children // when its "per source" create a context for each child individually IEnumerable <GraphFieldExecutionContext> childContexts = this.CreateChildExecutionContexts( context, childInvocationContext, sourceItemsToInclude); // Step 3 // -------------------- // Fire off the contexts through the pipeline foreach (var childContext in childContexts) { var task = _fieldExecutionPipeline.InvokeAsync(childContext, cancelToken) .ContinueWith(invokeTask => { this.CaptureChildFieldExecutionResults(context, childContext, invokeTask); }); pipelines.Add(task); if (_awaitEachPipeline) { await task.ConfigureAwait(false); } } } // wait for every pipeline to finish await Task.WhenAll(pipelines).ConfigureAwait(false); // reawait to allow for unwrapping and throwing of internal exceptions if (!_awaitEachPipeline) { foreach (var task in pipelines.Where(x => x.IsFaulted)) { await task.ConfigureAwait(false); } } }
private void CancelPipeline(GraphFieldExecutionContext context) { context.Cancel(); context.Request.DataSource.Items.ForEach(x => x.Cancel()); }
/// <summary> /// Called by the runtime to invoke the middleware logic. In most cases, the middleware component should return the task /// generated by calling the next delegate in the request chain with the provided context. /// </summary> /// <param name="context">The invocation request governing data in this pipeline run.</param> /// <param name="next">The next delegate in the chain to be invoked.</param> /// <param name="cancelToken">The cancel token.</param> /// <returns>Task.</returns> public async Task InvokeAsync(GraphFieldExecutionContext context, GraphMiddlewareInvocationDelegate <GraphFieldExecutionContext> next, CancellationToken cancelToken) { IEnumerable <IDirectiveInvocationContext> directives = context.Request.InvocationContext.Directives ?? Enumerable.Empty <IDirectiveInvocationContext>(); if (context.IsValid) { // generate requests contexts for each directive to be processed bool cancelPipeline = false; foreach (var directive in directives) { var directiveArguments = directive.Arguments.Merge(context.VariableData); var request = new GraphDirectiveRequest( directive.Directive, directive.Location, directive.Origin, context.Request.Items); var beforeResolutionRequest = request.ForLifeCycle( DirectiveLifeCycle.BeforeResolution, context.Request.DataSource); var directiveContext = new DirectiveResolutionContext(context, beforeResolutionRequest, directiveArguments); await this.ExecuteDirective(directiveContext, cancelToken).ConfigureAwait(false); context.Messages.AddRange(directiveContext.Messages); cancelPipeline = cancelPipeline || directiveContext.IsCancelled; } if (cancelPipeline) { this.CancelPipeline(context); } } // --------------------------------- // continue the pipeline // --------------------------------- await next(context, cancelToken).ConfigureAwait(false); // --------------------------------- // execute after completion directives // --------------------------------- if (context.IsValid) { bool cancelPipeline = false; foreach (var directive in directives) { var directiveArguments = directive.Arguments.Merge(context.VariableData); var request = new GraphDirectiveRequest( directive.Directive, directive.Location, directive.Origin, context.Request.Items); var afterResolutionRequest = request.ForLifeCycle( DirectiveLifeCycle.AfterResolution, context.Request.DataSource); var directiveContext = new DirectiveResolutionContext(context, afterResolutionRequest, directiveArguments); await this.ExecuteDirective(directiveContext, cancelToken).ConfigureAwait(false); context.Messages.AddRange(directiveContext.Messages); cancelPipeline = cancelPipeline || directiveContext.IsCancelled; } if (cancelPipeline) { this.CancelPipeline(context); } } }
/// <summary> /// For any resolved, non-leaf items assigned to the result, pass each through the resolution pipeline /// and await their individual results. /// </summary> /// <param name="context">The "parent" context supplying data for downstream reslts.</param> /// <param name="cancelToken">The cancel token.</param> /// <returns>Task.</returns> private async Task ProcessDownStreamFieldContexts(GraphFieldExecutionContext context, CancellationToken cancelToken) { if (context.InvocationContext.ChildContexts.Count == 0) { return; } var pipelines = new List <Task>(); // items resolved on this active context become the source for any downstream fields // --- // can never extract child fields from a null value (even if its valid for the item) // or one that isnt read for it IEnumerable <GraphDataItem> allSourceItems = context .ResolvedSourceItems .SelectMany(x => x.FlattenListItemTree()) .Where(x => x.ResultData != null && x.Status == FieldItemResolutionStatus.NeedsChildResolution); if (!allSourceItems.Any()) { return; } // create a lookup of source item by result type for easy seperation to the individual // downstream child contexts var sourceItemLookup = allSourceItems.ToLookup(x => x.ResultData.GetType()); IEnumerable <GraphFieldExecutionContext> childContexts = null; foreach (var childInvocationContext in context.InvocationContext.ChildContexts) { // Step 1 // ---------------------------- // figure out which child items need to be processed through it IEnumerable <GraphDataItem> sourceItemsToInclude; if (childInvocationContext.ExpectedSourceType == null) { sourceItemsToInclude = allSourceItems; } else { // if no children match the required type of the children present, then skip it // this can happen quite often in the case of a union or an interface where multiple invocation contexts // are added to a plan for the same child field in case a parent returns a member of the union or an // implementer of the interface if (!sourceItemLookup.Contains(childInvocationContext.ExpectedSourceType)) { continue; } sourceItemsToInclude = sourceItemLookup[childInvocationContext.ExpectedSourceType]; } // Step 2 // ---------------------------- // when the invocation is as a batch, create one execution context for all children // when its "per source" create a context for each child individually childContexts = this.CreateChildExecutionContexts(context, childInvocationContext, sourceItemsToInclude); // Step 3 // -------------------- // Fire off the contexts through the pipeline foreach (var childContext in childContexts) { var task = _fieldExecutionPipeline.InvokeAsync(childContext, cancelToken) .ContinueWith(invokeTask => { context.Messages.AddRange(childContext.Messages); }); pipelines.Add(task); if (_awaitEachPipeline) { await task.ConfigureAwait(false); } } } // wait for every pipeline to finish await Task.WhenAll(pipelines).ConfigureAwait(false); // reawait to allow for unwrapping and throwing of internal exceptions if (!_awaitEachPipeline) { foreach (var task in pipelines.Where(x => x.IsFaulted)) { await task.ConfigureAwait(false); } } }
/// <summary> /// Executes the field pipeline for the given request, returning the raw, unaltered response. /// </summary> /// <param name="context">The context.</param> /// <returns>Task<IGraphPipelineResponse>.</returns> public async Task ExecuteField(GraphFieldExecutionContext context) { var pipeline = this.ServiceProvider.GetService <ISchemaPipeline <TSchema, GraphFieldExecutionContext> >(); await pipeline.InvokeAsync(context, default).ConfigureAwait(false); }
private async Task ExecuteOperation(GraphQueryExecutionContext context) { // create a cancelation sourc irrespective of the required timeout for exeucting this operation // this allows for indication of why a task was canceled (timeout or other user driven reason) // vs just "it was canceled" which allows for tighter error messages in the response. var operation = context.QueryOperation; var fieldInvocations = new List <FieldPipelineInvocation>(); // Convert the supplied variable values to usable objects of the type expression // of the chosen operation var variableResolver = new ResolvedVariableGenerator(_schema, operation); var variableData = variableResolver.Resolve(context.Request.VariableData); var cancelSource = new CancellationTokenSource(); try { // begin a field execution pipeline for each top level field foreach (var invocationContext in operation.FieldContexts) { var path = new SourcePath(); path.AddFieldName(invocationContext.Name); object dataSourceValue; // fetch the source data value to use for the field invocation // attempt to retrieve from the master context if it was supplied by the pipeline // invoker, otherwise generate a root source if (!context.DefaultFieldSources.TryRetrieveSource(invocationContext.Field, out dataSourceValue)) { dataSourceValue = this.GenerateRootSourceData(operation.OperationType); } var topLevelDataItem = new GraphDataItem(invocationContext, dataSourceValue, path); var sourceData = new GraphFieldDataSource(dataSourceValue, path, topLevelDataItem); var fieldRequest = new GraphFieldRequest( invocationContext, sourceData, new SourceOrigin(invocationContext.Origin.Location, path), context.Items); var fieldContext = new GraphFieldExecutionContext( context, fieldRequest, variableData, context.DefaultFieldSources); var fieldTask = _fieldExecutionPipeline.InvokeAsync(fieldContext, cancelSource.Token); var pipelineInvocation = new FieldPipelineInvocation() { Task = fieldTask, DataItem = topLevelDataItem, FieldContext = fieldContext, }; fieldInvocations.Add(pipelineInvocation); // top level mutation operatons must be executed in sequential order // https://graphql.github.io/graphql-spec/June2018/#sec-Mutation if (_awaitEachTask || operation.OperationType == GraphCollection.Mutation) { await fieldTask.ConfigureAwait(false); } } // await all the outstanding tasks or a configured timeout var fieldPipelineTasksWrapper = Task.WhenAll(fieldInvocations.Select(x => x.Task)); var timeOutTask = Task.Delay(_timeoutMs, cancelSource.Token); var completedTask = await Task.WhenAny(fieldPipelineTasksWrapper, timeOutTask).ConfigureAwait(false); var isTimedOut = completedTask == timeOutTask; var cancelationWasRequested = cancelSource.IsCancellationRequested; if (!isTimedOut) { // Field resolutions completed within the timeout period. // Consider that the task may have faulted or been canceled causing them to complete incorrectly. // "re-await" so that any exceptions/cancellation are rethrown correctly. // and not aggregated under the `WhenAll/WhenAny` task from above // https://stackoverflow.com/questions/4238345/asynchronously-wait-for-taskt-to-complete-with-timeout foreach (var invocation in fieldInvocations) { await invocation.Task.ConfigureAwait(false); // load the reslts of each field (in order) to the context // for further processing context.FieldResults.Add(invocation.DataItem); context.Messages.AddRange(invocation.FieldContext.Messages); } } else { // when the timeout finishes first, process the cancel token in case any outstanding tasks are running // helps in cases where the timeout finished first but any of the field resolutions are perhaps stuck open // instruct all outstanding tasks to clean them selves up at the earlest possible point if (!cancelationWasRequested) { cancelSource.Cancel(); } } if (cancelationWasRequested) { context.Messages.Critical("The execution was canceled prior to completion of the requested query.", Constants.ErrorCodes.OPERATION_CANCELED); } else if (isTimedOut) { context.Messages.Critical($"The execution timed out prior to completion of the requested query. (Total Time: {_timeoutMs}ms)", Constants.ErrorCodes.OPERATION_CANCELED); } } finally { cancelSource.Dispose(); } }
public Task InvokeAsync(GraphFieldExecutionContext context, GraphMiddlewareInvocationDelegate <GraphFieldExecutionContext> next, CancellationToken cancelToken) { _testService.BeforeNext(this.Id); return(next(context, cancelToken)); }
public Task InvokeAsync(GraphFieldExecutionContext context, GraphMiddlewareInvocationDelegate <GraphFieldExecutionContext> next, CancellationToken cancelToken) { return(Task.FromException(new TestMiddlewareException())); }
/// <summary> /// Using the child context being invoked, this method creates the execution contexts in a manner /// that is expected by the invocation context be that 1 per each item, or 1 for a collective set of items being batched. /// </summary> /// <param name="context">The context.</param> /// <param name="childInvocationContext">The child invocation context.</param> /// <param name="sourceItemsToInclude">The source items to include.</param> /// <returns>IEnumerable<GraphFieldExecutionContext>.</returns> private IEnumerable <GraphFieldExecutionContext> CreateChildExecutionContexts( GraphFieldExecutionContext context, IGraphFieldInvocationContext childInvocationContext, IEnumerable <GraphDataItem> sourceItemsToInclude) { if (childInvocationContext.Field.Mode == FieldResolutionMode.PerSourceItem) { foreach (var sourceItem in sourceItemsToInclude) { var child = sourceItem.AddChildField(childInvocationContext); var dataSource = new GraphFieldDataSource(sourceItem.ResultData, child.Origin.Path, child); var request = new GraphFieldRequest(childInvocationContext, dataSource, child.Origin, context.Request.Items); yield return(new GraphFieldExecutionContext( context, request, context.VariableData, context.DefaultFieldSources)); } } else if (childInvocationContext.Field.Mode == FieldResolutionMode.Batch) { // remove any potential indexers from the path to this batch operation // in general this will be acted on a collection of items, attempt to remove // the first found instance of an indexer in the chain to indicate the path to the batch // // items may be declared as: Top.Parent[0].BatchField, Top.Parent[1].BatchField // alter the canonical path to be: Top.Parent.BatchField var fieldPath = sourceItemsToInclude.First().Origin.Path.Clone(); while (fieldPath.IsIndexedItem) { fieldPath = fieldPath.MakeParent(); } fieldPath.AddFieldName(childInvocationContext.Field.Name); var batchOrigin = new SourceOrigin(context.Request.Origin.Location, fieldPath); // create a list to house the raw source data being passed for the batch // this is the IEnumerable<T> required as an input to any batch resolver var sourceArgumentType = childInvocationContext.Field.Arguments.SourceDataArgument?.ObjectType ?? typeof(object); var sourceListType = typeof(List <>).MakeGenericType(sourceArgumentType); var sourceDataList = InstanceFactory.CreateInstance(sourceListType) as IList; // create a list of all the GraphDataItems representing the field // being resolved per input item var sourceItemList = new List <GraphDataItem>(); foreach (var item in sourceItemsToInclude) { var childField = item.AddChildField(childInvocationContext); sourceDataList.Add(item.ResultData); sourceItemList.Add(childField); } var dataSource = new GraphFieldDataSource( sourceDataList, fieldPath, sourceItemList); var request = new GraphFieldRequest(childInvocationContext, dataSource, batchOrigin, context.Request.Items); yield return(new GraphFieldExecutionContext( context, request, context.VariableData, context.DefaultFieldSources)); } else { throw new ArgumentOutOfRangeException( nameof(childInvocationContext.Field.Mode), $"The execution mode for field '{childInvocationContext.Field.Route.Path}' cannot be processed " + $"by {nameof(ProcessChildFieldsMiddleware<TSchema>)}. (Mode: {childInvocationContext.Field.Mode.ToString()})"); } }