diff --git a/src/main/java/graphql/EngineRunningState.java b/src/main/java/graphql/EngineRunningState.java new file mode 100644 index 000000000..965fdb59f --- /dev/null +++ b/src/main/java/graphql/EngineRunningState.java @@ -0,0 +1,209 @@ +package graphql; + +import graphql.execution.EngineRunningObserver; +import graphql.execution.ExecutionId; +import org.jspecify.annotations.Nullable; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Supplier; + +import static graphql.Assert.assertTrue; +import static graphql.execution.EngineRunningObserver.RunningState.NOT_RUNNING; +import static graphql.execution.EngineRunningObserver.RunningState.RUNNING; + +@Internal +public class EngineRunningState { + + @Nullable + private final EngineRunningObserver engineRunningObserver; + @Nullable + private final GraphQLContext graphQLContext; + @Nullable + private volatile ExecutionId executionId; + + private final AtomicInteger isRunning = new AtomicInteger(0); + + @VisibleForTesting + public EngineRunningState() { + this.engineRunningObserver = null; + this.graphQLContext = null; + this.executionId = null; + } + + public EngineRunningState(ExecutionInput executionInput) { + EngineRunningObserver engineRunningObserver = executionInput.getGraphQLContext().get(EngineRunningObserver.ENGINE_RUNNING_OBSERVER_KEY); + if (engineRunningObserver != null) { + this.engineRunningObserver = engineRunningObserver; + this.graphQLContext = executionInput.getGraphQLContext(); + this.executionId = executionInput.getExecutionId(); + } else { + this.engineRunningObserver = null; + this.graphQLContext = null; + this.executionId = null; + } + } + + public CompletableFuture handle(CompletableFuture src, BiFunction fn) { + if (engineRunningObserver == null) { + return src.handle(fn); + } + src = observeCompletableFutureStart(src); + CompletableFuture result = src.handle((t, throwable) -> { + // because we added an artificial dependent CF on src (in observeCompletableFutureStart) , a throwable is a CompletionException + // that needs to be unwrapped + if (throwable != null) { + throwable = throwable.getCause(); + } + return fn.apply(t, throwable); + }); + observerCompletableFutureEnd(src); + return result; + } + + public CompletableFuture whenComplete(CompletableFuture src, BiConsumer fn) { + if (engineRunningObserver == null) { + return src.whenComplete(fn); + } + src = observeCompletableFutureStart(src); + CompletableFuture result = src.whenComplete((t, throwable) -> { + // because we added an artificial dependent CF on src (in observeCompletableFutureStart) , a throwable is a CompletionException + // that needs to be unwrapped + if (throwable != null) { + throwable = throwable.getCause(); + } + fn.accept(t, throwable); + }); + observerCompletableFutureEnd(src); + return result; + } + + public CompletableFuture compose(CompletableFuture src, Function> fn) { + if (engineRunningObserver == null) { + return src.thenCompose(fn); + } + CompletableFuture result = new CompletableFuture<>(); + src = observeCompletableFutureStart(src); + src.whenComplete((u, t) -> { + CompletionStage innerCF; + try { + innerCF = fn.apply(u).toCompletableFuture(); + } catch (Throwable e) { + innerCF = CompletableFuture.failedFuture(e); + } + // this run is needed to wrap around the result.complete()/result.completeExceptionally() call + innerCF.whenComplete((u1, t1) -> run(() -> { + if (t1 != null) { + result.completeExceptionally(t1); + } else { + result.complete(u1); + } + })); + }); + observerCompletableFutureEnd(src); + return result; + } + + + private CompletableFuture observeCompletableFutureStart(CompletableFuture future) { + if (engineRunningObserver == null) { + return future; + } + // the completion order of dependent CFs is in stack order for + // directly dependent CFs, but in reverse stack order for indirect dependent ones + // By creating one dependent CF on originalFetchValue, we make sure the order it is always + // in reverse stack order + future = future.thenApply(Function.identity()); + incrementRunningWhenCompleted(future); + return future; + } + + private void observerCompletableFutureEnd(CompletableFuture future) { + if (engineRunningObserver == null) { + return; + } + decrementRunningWhenCompleted(future); + } + + + private void incrementRunningWhenCompleted(CompletableFuture cf) { + cf.whenComplete((result, throwable) -> { + incrementRunning(); + }); + } + + private void decrementRunningWhenCompleted(CompletableFuture cf) { + cf.whenComplete((result, throwable) -> { + decrementRunning(); + }); + + } + + private void decrementRunning() { + if (engineRunningObserver == null) { + return; + } + assertTrue(isRunning.get() > 0); + if (isRunning.decrementAndGet() == 0) { + changeOfState(NOT_RUNNING); + } + } + + + private void incrementRunning() { + if (engineRunningObserver == null) { + return; + } + assertTrue(isRunning.get() >= 0); + if (isRunning.incrementAndGet() == 1) { + changeOfState(RUNNING); + } + + } + + + public void updateExecutionId(ExecutionId executionId) { + if (engineRunningObserver == null) { + return; + } + this.executionId = executionId; + } + + private void changeOfState(EngineRunningObserver.RunningState runningState) { + engineRunningObserver.runningStateChanged(executionId, graphQLContext, runningState); + } + + private void run(Runnable runnable) { + if (engineRunningObserver == null) { + runnable.run(); + return; + } + incrementRunning(); + try { + runnable.run(); + } finally { + decrementRunning(); + } + } + + /** + * Only used once outside of this class: when the execution starts + */ + public T call(Supplier supplier) { + if (engineRunningObserver == null) { + return supplier.get(); + } + incrementRunning(); + try { + return supplier.get(); + } finally { + decrementRunning(); + } + } + + +} diff --git a/src/main/java/graphql/GraphQL.java b/src/main/java/graphql/GraphQL.java index 4443ed9f7..8c077a2a5 100644 --- a/src/main/java/graphql/GraphQL.java +++ b/src/main/java/graphql/GraphQL.java @@ -412,41 +412,44 @@ public CompletableFuture executeAsync(UnaryOperator executeAsync(ExecutionInput executionInput) { - ExecutionInput executionInputWithId = ensureInputHasId(executionInput); - - CompletableFuture instrumentationStateCF = instrumentation.createStateAsync(new InstrumentationCreateStateParameters(this.graphQLSchema, executionInputWithId)); - return Async.orNullCompletedFuture(instrumentationStateCF).thenCompose(instrumentationState -> { - try { - InstrumentationExecutionParameters inputInstrumentationParameters = new InstrumentationExecutionParameters(executionInputWithId, this.graphQLSchema); - ExecutionInput instrumentedExecutionInput = instrumentation.instrumentExecutionInput(executionInputWithId, inputInstrumentationParameters, instrumentationState); - - InstrumentationExecutionParameters instrumentationParameters = new InstrumentationExecutionParameters(instrumentedExecutionInput, this.graphQLSchema); - InstrumentationContext executionInstrumentation = nonNullCtx(instrumentation.beginExecution(instrumentationParameters, instrumentationState)); - executionInstrumentation.onDispatched(); - - GraphQLSchema graphQLSchema = instrumentation.instrumentSchema(this.graphQLSchema, instrumentationParameters, instrumentationState); - - CompletableFuture executionResult = parseValidateAndExecute(instrumentedExecutionInput, graphQLSchema, instrumentationState); - // - // finish up instrumentation - executionResult = executionResult.whenComplete(completeInstrumentationCtxCF(executionInstrumentation)); - // - // allow instrumentation to tweak the result - executionResult = executionResult.thenCompose(result -> instrumentation.instrumentExecutionResult(result, instrumentationParameters, instrumentationState)); - return executionResult; - } catch (AbortExecutionException abortException) { - return handleAbortException(executionInput, instrumentationState, abortException); - } + EngineRunningState engineRunningState = new EngineRunningState(executionInput); + return engineRunningState.call(() -> { + ExecutionInput executionInputWithId = ensureInputHasId(executionInput); + engineRunningState.updateExecutionId(executionInputWithId.getExecutionId()); + + CompletableFuture instrumentationStateCF = instrumentation.createStateAsync(new InstrumentationCreateStateParameters(this.graphQLSchema, executionInputWithId)); + instrumentationStateCF = Async.orNullCompletedFuture(instrumentationStateCF); + + return engineRunningState.compose(instrumentationStateCF, (instrumentationState -> { + try { + InstrumentationExecutionParameters inputInstrumentationParameters = new InstrumentationExecutionParameters(executionInputWithId, this.graphQLSchema); + ExecutionInput instrumentedExecutionInput = instrumentation.instrumentExecutionInput(executionInputWithId, inputInstrumentationParameters, instrumentationState); + + InstrumentationExecutionParameters instrumentationParameters = new InstrumentationExecutionParameters(instrumentedExecutionInput, this.graphQLSchema); + InstrumentationContext executionInstrumentation = nonNullCtx(instrumentation.beginExecution(instrumentationParameters, instrumentationState)); + executionInstrumentation.onDispatched(); + + GraphQLSchema graphQLSchema = instrumentation.instrumentSchema(this.graphQLSchema, instrumentationParameters, instrumentationState); + + CompletableFuture executionResult = parseValidateAndExecute(instrumentedExecutionInput, graphQLSchema, instrumentationState, engineRunningState); + // + // finish up instrumentation + executionResult = executionResult.whenComplete(completeInstrumentationCtxCF(executionInstrumentation)); + // + // allow instrumentation to tweak the result + executionResult = engineRunningState.compose(executionResult, (result -> instrumentation.instrumentExecutionResult(result, instrumentationParameters, instrumentationState))); + return executionResult; + } catch (AbortExecutionException abortException) { + return handleAbortException(executionInput, instrumentationState, abortException); + } + })); }); } + private CompletableFuture handleAbortException(ExecutionInput executionInput, InstrumentationState instrumentationState, AbortExecutionException abortException) { - CompletableFuture executionResult = CompletableFuture.completedFuture(abortException.toExecutionResult()); InstrumentationExecutionParameters instrumentationParameters = new InstrumentationExecutionParameters(executionInput, this.graphQLSchema); - // - // allow instrumentation to tweak the result - executionResult = executionResult.thenCompose(result -> instrumentation.instrumentExecutionResult(result, instrumentationParameters, instrumentationState)); - return executionResult; + return instrumentation.instrumentExecutionResult(abortException.toExecutionResult(), instrumentationParameters, instrumentationState); } private ExecutionInput ensureInputHasId(ExecutionInput executionInput) { @@ -460,7 +463,7 @@ private ExecutionInput ensureInputHasId(ExecutionInput executionInput) { } - private CompletableFuture parseValidateAndExecute(ExecutionInput executionInput, GraphQLSchema graphQLSchema, InstrumentationState instrumentationState) { + private CompletableFuture parseValidateAndExecute(ExecutionInput executionInput, GraphQLSchema graphQLSchema, InstrumentationState instrumentationState, EngineRunningState engineRunningState) { AtomicReference executionInputRef = new AtomicReference<>(executionInput); Function computeFunction = transformedInput -> { // if they change the original query in the pre-parser, then we want to see it downstream from then on @@ -468,16 +471,16 @@ private CompletableFuture parseValidateAndExecute(ExecutionInpu return parseAndValidate(executionInputRef, graphQLSchema, instrumentationState); }; CompletableFuture preparsedDoc = preparsedDocumentProvider.getDocumentAsync(executionInput, computeFunction); - return preparsedDoc.thenCompose(preparsedDocumentEntry -> { + return engineRunningState.compose(preparsedDoc, (preparsedDocumentEntry -> { if (preparsedDocumentEntry.hasErrors()) { return CompletableFuture.completedFuture(new ExecutionResultImpl(preparsedDocumentEntry.getErrors())); } try { - return execute(executionInputRef.get(), preparsedDocumentEntry.getDocument(), graphQLSchema, instrumentationState); + return execute(executionInputRef.get(), preparsedDocumentEntry.getDocument(), graphQLSchema, instrumentationState, engineRunningState); } catch (AbortExecutionException e) { return CompletableFuture.completedFuture(e.toExecutionResult()); } - }); + })); } private PreparsedDocumentEntry parseAndValidate(AtomicReference executionInputRef, GraphQLSchema graphQLSchema, InstrumentationState instrumentationState) { @@ -536,13 +539,14 @@ private List validate(ExecutionInput executionInput, Document d private CompletableFuture execute(ExecutionInput executionInput, Document document, GraphQLSchema graphQLSchema, - InstrumentationState instrumentationState + InstrumentationState instrumentationState, + EngineRunningState engineRunningState ) { Execution execution = new Execution(queryStrategy, mutationStrategy, subscriptionStrategy, instrumentation, valueUnboxer, doNotAutomaticallyDispatchDataLoader); ExecutionId executionId = executionInput.getExecutionId(); - return execution.execute(document, graphQLSchema, executionId, executionInput, instrumentationState); + return execution.execute(document, graphQLSchema, executionId, executionInput, instrumentationState, engineRunningState); } } diff --git a/src/main/java/graphql/execution/AbstractAsyncExecutionStrategy.java b/src/main/java/graphql/execution/AbstractAsyncExecutionStrategy.java index 1ff715125..6ae7f851e 100644 --- a/src/main/java/graphql/execution/AbstractAsyncExecutionStrategy.java +++ b/src/main/java/graphql/execution/AbstractAsyncExecutionStrategy.java @@ -22,7 +22,7 @@ public AbstractAsyncExecutionStrategy(DataFetcherExceptionHandler dataFetcherExc } protected BiConsumer, Throwable> handleResults(ExecutionContext executionContext, List fieldNames, CompletableFuture overallResult) { - return (List results, Throwable exception) -> executionContext.run(exception, () -> { + return (List results, Throwable exception) -> { if (exception != null) { handleNonNullException(executionContext, overallResult, exception); return; @@ -35,6 +35,6 @@ protected BiConsumer, Throwable> handleResults(ExecutionContext exe resolvedValuesByField.put(fieldName, result); } overallResult.complete(new ExecutionResultImpl(resolvedValuesByField, executionContext.getErrors())); - }); + }; } } diff --git a/src/main/java/graphql/execution/AsyncExecutionStrategy.java b/src/main/java/graphql/execution/AsyncExecutionStrategy.java index 138e4fb97..f7734df9f 100644 --- a/src/main/java/graphql/execution/AsyncExecutionStrategy.java +++ b/src/main/java/graphql/execution/AsyncExecutionStrategy.java @@ -38,58 +38,55 @@ public AsyncExecutionStrategy(DataFetcherExceptionHandler exceptionHandler) { @Override @SuppressWarnings("FutureReturnValueIgnored") public CompletableFuture execute(ExecutionContext executionContext, ExecutionStrategyParameters parameters) throws NonNullableFieldWasNullException { - return executionContext.call(() -> { - DataLoaderDispatchStrategy dataLoaderDispatcherStrategy = executionContext.getDataLoaderDispatcherStrategy(); - dataLoaderDispatcherStrategy.executionStrategy(executionContext, parameters); - Instrumentation instrumentation = executionContext.getInstrumentation(); - InstrumentationExecutionStrategyParameters instrumentationParameters = new InstrumentationExecutionStrategyParameters(executionContext, parameters); + DataLoaderDispatchStrategy dataLoaderDispatcherStrategy = executionContext.getDataLoaderDispatcherStrategy(); + dataLoaderDispatcherStrategy.executionStrategy(executionContext, parameters); + Instrumentation instrumentation = executionContext.getInstrumentation(); + InstrumentationExecutionStrategyParameters instrumentationParameters = new InstrumentationExecutionStrategyParameters(executionContext, parameters); - ExecutionStrategyInstrumentationContext executionStrategyCtx = ExecutionStrategyInstrumentationContext.nonNullCtx(instrumentation.beginExecutionStrategy(instrumentationParameters, executionContext.getInstrumentationState())); + ExecutionStrategyInstrumentationContext executionStrategyCtx = ExecutionStrategyInstrumentationContext.nonNullCtx(instrumentation.beginExecutionStrategy(instrumentationParameters, executionContext.getInstrumentationState())); - MergedSelectionSet fields = parameters.getFields(); - List fieldNames = fields.getKeys(); + MergedSelectionSet fields = parameters.getFields(); + List fieldNames = fields.getKeys(); - Optional isNotSensible = Introspection.isIntrospectionSensible(fields, executionContext); - if (isNotSensible.isPresent()) { - return CompletableFuture.completedFuture(isNotSensible.get()); + Optional isNotSensible = Introspection.isIntrospectionSensible(fields, executionContext); + if (isNotSensible.isPresent()) { + return CompletableFuture.completedFuture(isNotSensible.get()); + } + + DeferredExecutionSupport deferredExecutionSupport = createDeferredExecutionSupport(executionContext, parameters); + Async.CombinedBuilder futures = getAsyncFieldValueInfo(executionContext, parameters, deferredExecutionSupport); + + CompletableFuture overallResult = new CompletableFuture<>(); + executionStrategyCtx.onDispatched(); + + futures.await().whenComplete((completeValueInfos, throwable) -> { + List fieldsExecutedOnInitialResult = deferredExecutionSupport.getNonDeferredFieldNames(fieldNames); + + BiConsumer, Throwable> handleResultsConsumer = handleResults(executionContext, fieldsExecutedOnInitialResult, overallResult); + if (throwable != null) { + handleResultsConsumer.accept(null, throwable.getCause()); + return; } - DeferredExecutionSupport deferredExecutionSupport = createDeferredExecutionSupport(executionContext, parameters); - Async.CombinedBuilder futures = getAsyncFieldValueInfo(executionContext, parameters, deferredExecutionSupport); - - CompletableFuture overallResult = new CompletableFuture<>(); - executionStrategyCtx.onDispatched(); - - futures.await().whenComplete((completeValueInfos, throwable) -> { - executionContext.run(throwable,() -> { - List fieldsExecutedOnInitialResult = deferredExecutionSupport.getNonDeferredFieldNames(fieldNames); - - BiConsumer, Throwable> handleResultsConsumer = handleResults(executionContext, fieldsExecutedOnInitialResult, overallResult); - if (throwable != null) { - handleResultsConsumer.accept(null, throwable.getCause()); - return; - } - - Async.CombinedBuilder fieldValuesFutures = Async.ofExpectedSize(completeValueInfos.size()); - for (FieldValueInfo completeValueInfo : completeValueInfos) { - fieldValuesFutures.addObject(completeValueInfo.getFieldValueObject()); - } - dataLoaderDispatcherStrategy.executionStrategyOnFieldValuesInfo(completeValueInfos); - executionStrategyCtx.onFieldValuesInfo(completeValueInfos); - fieldValuesFutures.await().whenComplete(handleResultsConsumer); - }); - }).exceptionally((ex) -> executionContext.call(ex,() -> { - // if there are any issues with combining/handling the field results, - // complete the future at all costs and bubble up any thrown exception so - // the execution does not hang. - dataLoaderDispatcherStrategy.executionStrategyOnFieldValuesException(ex); - executionStrategyCtx.onFieldValuesException(); - overallResult.completeExceptionally(ex); - return null; - })); - - overallResult.whenComplete(executionStrategyCtx::onCompleted); - return overallResult; + Async.CombinedBuilder fieldValuesFutures = Async.ofExpectedSize(completeValueInfos.size()); + for (FieldValueInfo completeValueInfo : completeValueInfos) { + fieldValuesFutures.addObject(completeValueInfo.getFieldValueObject()); + } + dataLoaderDispatcherStrategy.executionStrategyOnFieldValuesInfo(completeValueInfos); + executionStrategyCtx.onFieldValuesInfo(completeValueInfos); + fieldValuesFutures.await().whenComplete(handleResultsConsumer); + }).exceptionally((ex) -> { + // if there are any issues with combining/handling the field results, + // complete the future at all costs and bubble up any thrown exception so + // the execution does not hang. + dataLoaderDispatcherStrategy.executionStrategyOnFieldValuesException(ex); + executionStrategyCtx.onFieldValuesException(); + overallResult.completeExceptionally(ex); + return null; }); + + overallResult.whenComplete(executionStrategyCtx::onCompleted); + return overallResult; } + } diff --git a/src/main/java/graphql/execution/AsyncSerialExecutionStrategy.java b/src/main/java/graphql/execution/AsyncSerialExecutionStrategy.java index 040bd12a8..545d0fb0a 100644 --- a/src/main/java/graphql/execution/AsyncSerialExecutionStrategy.java +++ b/src/main/java/graphql/execution/AsyncSerialExecutionStrategy.java @@ -32,41 +32,39 @@ public AsyncSerialExecutionStrategy(DataFetcherExceptionHandler exceptionHandler @Override @SuppressWarnings({"TypeParameterUnusedInFormals", "FutureReturnValueIgnored"}) public CompletableFuture execute(ExecutionContext executionContext, ExecutionStrategyParameters parameters) throws NonNullableFieldWasNullException { - return executionContext.call(() -> { - DataLoaderDispatchStrategy dataLoaderDispatcherStrategy = executionContext.getDataLoaderDispatcherStrategy(); + DataLoaderDispatchStrategy dataLoaderDispatcherStrategy = executionContext.getDataLoaderDispatcherStrategy(); - Instrumentation instrumentation = executionContext.getInstrumentation(); - InstrumentationExecutionStrategyParameters instrumentationParameters = new InstrumentationExecutionStrategyParameters(executionContext, parameters); - InstrumentationContext executionStrategyCtx = nonNullCtx(instrumentation.beginExecutionStrategy(instrumentationParameters, - executionContext.getInstrumentationState()) - ); - MergedSelectionSet fields = parameters.getFields(); - ImmutableList fieldNames = ImmutableList.copyOf(fields.keySet()); + Instrumentation instrumentation = executionContext.getInstrumentation(); + InstrumentationExecutionStrategyParameters instrumentationParameters = new InstrumentationExecutionStrategyParameters(executionContext, parameters); + InstrumentationContext executionStrategyCtx = nonNullCtx(instrumentation.beginExecutionStrategy(instrumentationParameters, + executionContext.getInstrumentationState()) + ); + MergedSelectionSet fields = parameters.getFields(); + ImmutableList fieldNames = ImmutableList.copyOf(fields.keySet()); - // this is highly unlikely since Mutations cant do introspection BUT in theory someone could make the query strategy this code - // so belts and braces - Optional isNotSensible = Introspection.isIntrospectionSensible(fields, executionContext); - if (isNotSensible.isPresent()) { - return CompletableFuture.completedFuture(isNotSensible.get()); - } + // this is highly unlikely since Mutations cant do introspection BUT in theory someone could make the query strategy this code + // so belts and braces + Optional isNotSensible = Introspection.isIntrospectionSensible(fields, executionContext); + if (isNotSensible.isPresent()) { + return CompletableFuture.completedFuture(isNotSensible.get()); + } - CompletableFuture> resultsFuture = Async.eachSequentially(fieldNames, (fieldName, prevResults) -> executionContext.call(() -> { - MergedField currentField = fields.getSubField(fieldName); - ResultPath fieldPath = parameters.getPath().segment(mkNameForPath(currentField)); - ExecutionStrategyParameters newParameters = parameters - .transform(builder -> builder.field(currentField).path(fieldPath)); + CompletableFuture> resultsFuture = Async.eachSequentially(fieldNames, (fieldName, prevResults) -> { + MergedField currentField = fields.getSubField(fieldName); + ResultPath fieldPath = parameters.getPath().segment(mkNameForPath(currentField)); + ExecutionStrategyParameters newParameters = parameters + .transform(builder -> builder.field(currentField).path(fieldPath)); - Object resolveSerialField = resolveSerialField(executionContext, dataLoaderDispatcherStrategy, newParameters); - return resolveSerialField; - })); + Object resolveSerialField = resolveSerialField(executionContext, dataLoaderDispatcherStrategy, newParameters); + return resolveSerialField; + }); - CompletableFuture overallResult = new CompletableFuture<>(); - executionStrategyCtx.onDispatched(); + CompletableFuture overallResult = new CompletableFuture<>(); + executionStrategyCtx.onDispatched(); - resultsFuture.whenComplete(handleResults(executionContext, fieldNames, overallResult)); - overallResult.whenComplete(executionStrategyCtx::onCompleted); - return overallResult; - }); + resultsFuture.whenComplete(handleResults(executionContext, fieldNames, overallResult)); + overallResult.whenComplete(executionStrategyCtx::onCompleted); + return overallResult; } private Object resolveSerialField(ExecutionContext executionContext, @@ -77,11 +75,11 @@ private Object resolveSerialField(ExecutionContext executionContext, Object fieldWithInfo = resolveFieldWithInfo(executionContext, newParameters); if (fieldWithInfo instanceof CompletableFuture) { //noinspection unchecked - return ((CompletableFuture) fieldWithInfo).thenCompose(fvi -> executionContext.call(() -> { + return ((CompletableFuture) fieldWithInfo).thenCompose(fvi -> { dataLoaderDispatcherStrategy.executionStrategyOnFieldValuesInfo(List.of(fvi)); CompletableFuture fieldValueFuture = fvi.getFieldValueFuture(); return fieldValueFuture; - })); + }); } else { FieldValueInfo fvi = (FieldValueInfo) fieldWithInfo; dataLoaderDispatcherStrategy.executionStrategyOnFieldValuesInfo(List.of(fvi)); diff --git a/src/main/java/graphql/execution/Execution.java b/src/main/java/graphql/execution/Execution.java index 0373d6564..a784325f3 100644 --- a/src/main/java/graphql/execution/Execution.java +++ b/src/main/java/graphql/execution/Execution.java @@ -2,6 +2,7 @@ import graphql.Directives; +import graphql.EngineRunningState; import graphql.ExecutionInput; import graphql.ExecutionResult; import graphql.ExecutionResultImpl; @@ -72,7 +73,7 @@ public Execution(ExecutionStrategy queryStrategy, this.doNotAutomaticallyDispatchDataLoader = doNotAutomaticallyDispatchDataLoader; } - public CompletableFuture execute(Document document, GraphQLSchema graphQLSchema, ExecutionId executionId, ExecutionInput executionInput, InstrumentationState instrumentationState) { + public CompletableFuture execute(Document document, GraphQLSchema graphQLSchema, ExecutionId executionId, ExecutionInput executionInput, InstrumentationState instrumentationState, EngineRunningState engineRunningState) { NodeUtil.GetOperationResult getOperationResult; CoercedVariables coercedVariables; Supplier normalizedVariableValues; @@ -89,9 +90,6 @@ public CompletableFuture execute(Document document, GraphQLSche boolean propagateErrorsOnNonNullContractFailure = propagateErrorsOnNonNullContractFailure(getOperationResult.operationDefinition.getDirectives()); - // can be null - EngineRunningObserver engineRunningObserver = executionInput.getGraphQLContext().get(EngineRunningObserver.ENGINE_RUNNING_OBSERVER_KEY); - ExecutionContext executionContext = newExecutionContextBuilder() .instrumentation(instrumentation) .instrumentationState(instrumentationState) @@ -114,7 +112,7 @@ public CompletableFuture execute(Document document, GraphQLSche .valueUnboxer(valueUnboxer) .executionInput(executionInput) .propagapropagateErrorsOnNonNullContractFailureeErrors(propagateErrorsOnNonNullContractFailure) - .engineRunningObserver(engineRunningObserver) + .engineRunningState(engineRunningState) .build(); executionContext.getGraphQLContext().put(ResultNodesInfo.RESULT_NODES_INFO, executionContext.getResultNodesInfo()); diff --git a/src/main/java/graphql/execution/ExecutionContext.java b/src/main/java/graphql/execution/ExecutionContext.java index 95abf8c6e..a53ae621e 100644 --- a/src/main/java/graphql/execution/ExecutionContext.java +++ b/src/main/java/graphql/execution/ExecutionContext.java @@ -3,6 +3,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import graphql.EngineRunningState; import graphql.ExecutionInput; import graphql.ExperimentalApi; import graphql.GraphQLContext; @@ -10,7 +11,6 @@ import graphql.Internal; import graphql.PublicApi; import graphql.collect.ImmutableKit; -import graphql.execution.EngineRunningObserver.RunningState; import graphql.execution.incremental.IncrementalCallState; import graphql.execution.instrumentation.Instrumentation; import graphql.execution.instrumentation.InstrumentationState; @@ -23,23 +23,17 @@ import graphql.util.FpKit; import graphql.util.LockKit; import org.dataloader.DataLoaderRegistry; -import org.jspecify.annotations.Nullable; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.function.Supplier; -import static graphql.Assert.assertTrue; -import static graphql.execution.EngineRunningObserver.RunningState.NOT_RUNNING; -import static graphql.execution.EngineRunningObserver.RunningState.RUNNING; - @SuppressWarnings("TypeParameterUnusedInFormals") @PublicApi public class ExecutionContext { @@ -77,7 +71,7 @@ public class ExecutionContext { private volatile DataLoaderDispatchStrategy dataLoaderDispatcherStrategy = DataLoaderDispatchStrategy.NO_OP; private final ResultNodesInfo resultNodesInfo = new ResultNodesInfo(); - private final EngineRunningObserver engineRunningObserver; + private final EngineRunningState engineRunningState; ExecutionContext(ExecutionContextBuilder builder) { this.graphQLSchema = builder.graphQLSchema; @@ -104,7 +98,7 @@ public class ExecutionContext { this.dataLoaderDispatcherStrategy = builder.dataLoaderDispatcherStrategy; this.queryTree = FpKit.interThreadMemoize(() -> ExecutableNormalizedOperationFactory.createExecutableNormalizedOperation(graphQLSchema, operationDefinition, fragmentsByName, coercedVariables)); this.propagateErrorsOnNonNullContractFailure = builder.propagateErrorsOnNonNullContractFailure; - this.engineRunningObserver = builder.engineRunningObserver; + this.engineRunningState = builder.engineRunningState; } @@ -366,78 +360,9 @@ public ResultNodesInfo getResultNodesInfo() { return resultNodesInfo; } - @Nullable - EngineRunningObserver getEngineRunningObserver() { - return engineRunningObserver; - } - - @Internal - public boolean isRunning() { - return isRunning.get() > 0; - } - - private void incrementRunning(Throwable throwable) { - assertTrue(isRunning.get() >= 0); - if (isRunning.incrementAndGet() == 1) { - changeOfState(RUNNING); - } - } - - private void decrementRunning(Throwable throwable) { - assertTrue(isRunning.get() > 0); - if (isRunning.decrementAndGet() == 0) { - changeOfState(NOT_RUNNING); - } - } - - @Internal - public void incrementRunning(CompletableFuture cf) { - cf.whenComplete((result, throwable) -> { - incrementRunning(throwable); - }); - } - - @Internal - public void decrementRunning(CompletableFuture cf) { - cf.whenComplete((result, throwable) -> { - decrementRunning(throwable); - }); - - } - @Internal - public T call(Supplier callable) { - return call(null, callable); + public EngineRunningState getEngineRunningState() { + return engineRunningState; } - @Internal - public T call(Throwable throwable, Supplier callable) { - incrementRunning(throwable); - try { - return callable.get(); - } finally { - decrementRunning(throwable); - } - } - - @Internal - public void run(Runnable runnable) { - run(null, runnable); - } - - @Internal - public void run(Throwable throwable, Runnable runnable) { - incrementRunning(throwable); - try { - runnable.run(); - } finally { - decrementRunning(throwable); - } - } - - private void changeOfState(RunningState runningState) { - if (engineRunningObserver != null) { - engineRunningObserver.runningStateChanged(executionId, graphQLContext, runningState); - } - } } diff --git a/src/main/java/graphql/execution/ExecutionContextBuilder.java b/src/main/java/graphql/execution/ExecutionContextBuilder.java index fbd0cc7bf..014bab516 100644 --- a/src/main/java/graphql/execution/ExecutionContextBuilder.java +++ b/src/main/java/graphql/execution/ExecutionContextBuilder.java @@ -2,6 +2,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import graphql.EngineRunningState; import graphql.ExecutionInput; import graphql.ExperimentalApi; import graphql.GraphQLContext; @@ -50,7 +51,7 @@ public class ExecutionContextBuilder { ExecutionInput executionInput; DataLoaderDispatchStrategy dataLoaderDispatcherStrategy = DataLoaderDispatchStrategy.NO_OP; boolean propagateErrorsOnNonNullContractFailure = true; - EngineRunningObserver engineRunningObserver; + EngineRunningState engineRunningState; /** * @return a new builder of {@link graphql.execution.ExecutionContext}s @@ -98,7 +99,7 @@ public ExecutionContextBuilder() { executionInput = other.getExecutionInput(); dataLoaderDispatcherStrategy = other.getDataLoaderDispatcherStrategy(); propagateErrorsOnNonNullContractFailure = other.propagateErrorsOnNonNullContractFailure(); - engineRunningObserver = other.getEngineRunningObserver(); + engineRunningState = other.getEngineRunningState(); } public ExecutionContextBuilder instrumentation(Instrumentation instrumentation) { @@ -241,8 +242,8 @@ public ExecutionContext build() { return new ExecutionContext(this); } - public ExecutionContextBuilder engineRunningObserver(EngineRunningObserver engineRunningObserver) { - this.engineRunningObserver = engineRunningObserver; + public ExecutionContextBuilder engineRunningState(EngineRunningState engineRunningState) { + this.engineRunningState = engineRunningState; return this; } } diff --git a/src/main/java/graphql/execution/ExecutionStrategy.java b/src/main/java/graphql/execution/ExecutionStrategy.java index 44a086899..f9370d1e9 100644 --- a/src/main/java/graphql/execution/ExecutionStrategy.java +++ b/src/main/java/graphql/execution/ExecutionStrategy.java @@ -3,6 +3,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Maps; import graphql.DuckTyped; +import graphql.EngineRunningState; import graphql.ExecutionResult; import graphql.ExecutionResultImpl; import graphql.ExperimentalApi; @@ -201,73 +202,69 @@ public static String mkNameForPath(List currentField) { @SuppressWarnings("unchecked") @DuckTyped(shape = "CompletableFuture> | Map") protected Object executeObject(ExecutionContext executionContext, ExecutionStrategyParameters parameters) throws NonNullableFieldWasNullException { - return executionContext.call(() -> { - DataLoaderDispatchStrategy dataLoaderDispatcherStrategy = executionContext.getDataLoaderDispatcherStrategy(); - dataLoaderDispatcherStrategy.executeObject(executionContext, parameters); - Instrumentation instrumentation = executionContext.getInstrumentation(); - InstrumentationExecutionStrategyParameters instrumentationParameters = new InstrumentationExecutionStrategyParameters(executionContext, parameters); - - ExecuteObjectInstrumentationContext resolveObjectCtx = ExecuteObjectInstrumentationContext.nonNullCtx( - instrumentation.beginExecuteObject(instrumentationParameters, executionContext.getInstrumentationState()) - ); + DataLoaderDispatchStrategy dataLoaderDispatcherStrategy = executionContext.getDataLoaderDispatcherStrategy(); + dataLoaderDispatcherStrategy.executeObject(executionContext, parameters); + Instrumentation instrumentation = executionContext.getInstrumentation(); + InstrumentationExecutionStrategyParameters instrumentationParameters = new InstrumentationExecutionStrategyParameters(executionContext, parameters); - List fieldNames = parameters.getFields().getKeys(); - - DeferredExecutionSupport deferredExecutionSupport = createDeferredExecutionSupport(executionContext, parameters); - Async.CombinedBuilder resolvedFieldFutures = getAsyncFieldValueInfo(executionContext, parameters, deferredExecutionSupport); - - CompletableFuture> overallResult = new CompletableFuture<>(); - List fieldsExecutedOnInitialResult = deferredExecutionSupport.getNonDeferredFieldNames(fieldNames); - BiConsumer, Throwable> handleResultsConsumer = buildFieldValueMap(fieldsExecutedOnInitialResult, overallResult, executionContext); - - resolveObjectCtx.onDispatched(); - - Object fieldValueInfosResult = resolvedFieldFutures.awaitPolymorphic(); - if (fieldValueInfosResult instanceof CompletableFuture) { - CompletableFuture> fieldValueInfos = (CompletableFuture>) fieldValueInfosResult; - fieldValueInfos.whenComplete((completeValueInfos, throwable) -> { - executionContext.run(throwable, () -> { - if (throwable != null) { - handleResultsConsumer.accept(null, throwable); - return; - } - - Async.CombinedBuilder resultFutures = fieldValuesCombinedBuilder(completeValueInfos); - dataLoaderDispatcherStrategy.executeObjectOnFieldValuesInfo(completeValueInfos, parameters); - resolveObjectCtx.onFieldValuesInfo(completeValueInfos); - resultFutures.await().whenComplete(handleResultsConsumer); - }); - }).exceptionally((ex) -> executionContext.call(() -> { - // if there are any issues with combining/handling the field results, - // complete the future at all costs and bubble up any thrown exception so - // the execution does not hang. - dataLoaderDispatcherStrategy.executeObjectOnFieldValuesException(ex, parameters); - resolveObjectCtx.onFieldValuesException(); - overallResult.completeExceptionally(ex); - return null; - })); - overallResult.whenComplete(resolveObjectCtx::onCompleted); - return overallResult; - } else { - List completeValueInfos = (List) fieldValueInfosResult; + ExecuteObjectInstrumentationContext resolveObjectCtx = ExecuteObjectInstrumentationContext.nonNullCtx( + instrumentation.beginExecuteObject(instrumentationParameters, executionContext.getInstrumentationState()) + ); + + List fieldNames = parameters.getFields().getKeys(); + + DeferredExecutionSupport deferredExecutionSupport = createDeferredExecutionSupport(executionContext, parameters); + Async.CombinedBuilder resolvedFieldFutures = getAsyncFieldValueInfo(executionContext, parameters, deferredExecutionSupport); + + CompletableFuture> overallResult = new CompletableFuture<>(); + List fieldsExecutedOnInitialResult = deferredExecutionSupport.getNonDeferredFieldNames(fieldNames); + BiConsumer, Throwable> handleResultsConsumer = buildFieldValueMap(fieldsExecutedOnInitialResult, overallResult, executionContext); + + resolveObjectCtx.onDispatched(); + + Object fieldValueInfosResult = resolvedFieldFutures.awaitPolymorphic(); + if (fieldValueInfosResult instanceof CompletableFuture) { + CompletableFuture> fieldValueInfos = (CompletableFuture>) fieldValueInfosResult; + fieldValueInfos.whenComplete((completeValueInfos, throwable) -> { + if (throwable != null) { + handleResultsConsumer.accept(null, throwable); + return; + } Async.CombinedBuilder resultFutures = fieldValuesCombinedBuilder(completeValueInfos); dataLoaderDispatcherStrategy.executeObjectOnFieldValuesInfo(completeValueInfos, parameters); resolveObjectCtx.onFieldValuesInfo(completeValueInfos); + resultFutures.await().whenComplete(handleResultsConsumer); + }).exceptionally((ex) -> { + // if there are any issues with combining/handling the field results, + // complete the future at all costs and bubble up any thrown exception so + // the execution does not hang. + dataLoaderDispatcherStrategy.executeObjectOnFieldValuesException(ex, parameters); + resolveObjectCtx.onFieldValuesException(); + overallResult.completeExceptionally(ex); + return null; + }); + overallResult.whenComplete(resolveObjectCtx::onCompleted); + return overallResult; + } else { + List completeValueInfos = (List) fieldValueInfosResult; - Object completedValuesObject = resultFutures.awaitPolymorphic(); - if (completedValuesObject instanceof CompletableFuture) { - CompletableFuture> completedValues = (CompletableFuture>) completedValuesObject; - completedValues.whenComplete(handleResultsConsumer); - overallResult.whenComplete(resolveObjectCtx::onCompleted); - return overallResult; - } else { - Map fieldValueMap = buildFieldValueMap(fieldsExecutedOnInitialResult, (List) completedValuesObject); - resolveObjectCtx.onCompleted(fieldValueMap, null); - return fieldValueMap; - } + Async.CombinedBuilder resultFutures = fieldValuesCombinedBuilder(completeValueInfos); + dataLoaderDispatcherStrategy.executeObjectOnFieldValuesInfo(completeValueInfos, parameters); + resolveObjectCtx.onFieldValuesInfo(completeValueInfos); + + Object completedValuesObject = resultFutures.awaitPolymorphic(); + if (completedValuesObject instanceof CompletableFuture) { + CompletableFuture> completedValues = (CompletableFuture>) completedValuesObject; + completedValues.whenComplete(handleResultsConsumer); + overallResult.whenComplete(resolveObjectCtx::onCompleted); + return overallResult; + } else { + Map fieldValueMap = buildFieldValueMap(fieldsExecutedOnInitialResult, (List) completedValuesObject); + resolveObjectCtx.onCompleted(fieldValueMap, null); + return fieldValueMap; } - }); + } } private static Async.@NonNull CombinedBuilder fieldValuesCombinedBuilder(List completeValueInfos) { @@ -280,14 +277,12 @@ protected Object executeObject(ExecutionContext executionContext, ExecutionStrat private BiConsumer, Throwable> buildFieldValueMap(List fieldNames, CompletableFuture> overallResult, ExecutionContext executionContext) { return (List results, Throwable exception) -> { - executionContext.run(exception, () -> { - if (exception != null) { - handleValueException(overallResult, exception, executionContext); - return; - } - Map resolvedValuesByField = buildFieldValueMap(fieldNames, results); - overallResult.complete(resolvedValuesByField); - }); + if (exception != null) { + handleValueException(overallResult, exception, executionContext); + return; + } + Map resolvedValuesByField = buildFieldValueMap(fieldNames, results); + overallResult.complete(resolvedValuesByField); }; } @@ -485,32 +480,23 @@ private Object fetchField(GraphQLFieldDefinition fieldDef, ExecutionContext exec } if (fetchedObject instanceof CompletableFuture) { @SuppressWarnings("unchecked") - CompletableFuture originalFetchValue = (CompletableFuture) fetchedObject; - // the completion order of dependent CFs is in stack order for - // directly dependent CFs, but in reverse stack order for indirect dependent ones - // By creating one dependent CF on originalFetchValue, we make sure the order it is always - // in reverse stack order - CompletableFuture fetchedValue = originalFetchValue.thenApply(Function.identity()); - executionContext.incrementRunning(fetchedValue); - CompletableFuture rawResultCF = fetchedValue - .handle((result, wrapperExceptionOrNull) -> executionContext.call(wrapperExceptionOrNull, () -> { - // because we added an artificial CF, we need to unwrap the exception - Throwable exception = wrapperExceptionOrNull != null ? wrapperExceptionOrNull.getCause() : null; - fetchCtx.onCompleted(result, exception); - if (exception != null) { - CompletableFuture handleFetchingExceptionResult = handleFetchingException(dataFetchingEnvironment.get(), parameters, exception); - return handleFetchingExceptionResult; - } else { - // we can simply return the fetched value CF and avoid a allocation - return originalFetchValue; - } - })) - .thenCompose(Function.identity()); - executionContext.incrementRunning(rawResultCF); + CompletableFuture fetchedValue = (CompletableFuture) fetchedObject; + EngineRunningState engineRunningState = executionContext.getEngineRunningState(); + + CompletableFuture> handleCF = engineRunningState.handle(fetchedValue, (result, exception) -> { + // because we added an artificial CF, we need to unwrap the exception + fetchCtx.onCompleted(result, exception); + if (exception != null) { + CompletableFuture handleFetchingExceptionResult = handleFetchingException(dataFetchingEnvironment.get(), parameters, exception); + return handleFetchingExceptionResult; + } else { + // we can simply return the fetched value CF and avoid a allocation + return fetchedValue; + } + }); + CompletableFuture rawResultCF = engineRunningState.compose(handleCF, Function.identity()); CompletableFuture fetchedValueCF = rawResultCF .thenApply(result -> unboxPossibleDataFetcherResult(executionContext, parameters, result)); - executionContext.decrementRunning(rawResultCF); - executionContext.decrementRunning(fetchedValue); return fetchedValueCF; } else { fetchCtx.onCompleted(fetchedObject, null); @@ -546,26 +532,24 @@ protected Supplier getNormalizedField(ExecutionContex protected FetchedValue unboxPossibleDataFetcherResult(ExecutionContext executionContext, ExecutionStrategyParameters parameters, Object result) { - return executionContext.call(() -> { - if (result instanceof DataFetcherResult) { - DataFetcherResult dataFetcherResult = (DataFetcherResult) result; + if (result instanceof DataFetcherResult) { + DataFetcherResult dataFetcherResult = (DataFetcherResult) result; - addErrorsToRightContext(dataFetcherResult.getErrors(), parameters, executionContext); + addErrorsToRightContext(dataFetcherResult.getErrors(), parameters, executionContext); - addExtensionsIfPresent(executionContext, dataFetcherResult); + addExtensionsIfPresent(executionContext, dataFetcherResult); - Object localContext = dataFetcherResult.getLocalContext(); - if (localContext == null) { - // if the field returns nothing then they get the context of their parent field - localContext = parameters.getLocalContext(); - } - Object unBoxedValue = executionContext.getValueUnboxer().unbox(dataFetcherResult.getData()); - return new FetchedValue(unBoxedValue, dataFetcherResult.getErrors(), localContext); - } else { - Object unBoxedValue = executionContext.getValueUnboxer().unbox(result); - return new FetchedValue(unBoxedValue, ImmutableList.of(), parameters.getLocalContext()); + Object localContext = dataFetcherResult.getLocalContext(); + if (localContext == null) { + // if the field returns nothing then they get the context of their parent field + localContext = parameters.getLocalContext(); } - }); + Object unBoxedValue = executionContext.getValueUnboxer().unbox(dataFetcherResult.getData()); + return new FetchedValue(unBoxedValue, dataFetcherResult.getErrors(), localContext); + } else { + Object unBoxedValue = executionContext.getValueUnboxer().unbox(result); + return new FetchedValue(unBoxedValue, ImmutableList.of(), parameters.getLocalContext()); + } } private void addExtensionsIfPresent(ExecutionContext executionContext, DataFetcherResult dataFetcherResult) { @@ -632,32 +616,30 @@ protected FieldValueInfo completeField(ExecutionContext executionContext, Execut } private FieldValueInfo completeField(GraphQLFieldDefinition fieldDef, ExecutionContext executionContext, ExecutionStrategyParameters parameters, FetchedValue fetchedValue) { - return executionContext.call(() -> { - GraphQLObjectType parentType = (GraphQLObjectType) parameters.getExecutionStepInfo().getUnwrappedNonNullType(); - ExecutionStepInfo executionStepInfo = createExecutionStepInfo(executionContext, parameters, fieldDef, parentType); + GraphQLObjectType parentType = (GraphQLObjectType) parameters.getExecutionStepInfo().getUnwrappedNonNullType(); + ExecutionStepInfo executionStepInfo = createExecutionStepInfo(executionContext, parameters, fieldDef, parentType); - Instrumentation instrumentation = executionContext.getInstrumentation(); - InstrumentationFieldCompleteParameters instrumentationParams = new InstrumentationFieldCompleteParameters(executionContext, parameters, () -> executionStepInfo, fetchedValue); - InstrumentationContext ctxCompleteField = nonNullCtx(instrumentation.beginFieldCompletion( - instrumentationParams, executionContext.getInstrumentationState() - )); + Instrumentation instrumentation = executionContext.getInstrumentation(); + InstrumentationFieldCompleteParameters instrumentationParams = new InstrumentationFieldCompleteParameters(executionContext, parameters, () -> executionStepInfo, fetchedValue); + InstrumentationContext ctxCompleteField = nonNullCtx(instrumentation.beginFieldCompletion( + instrumentationParams, executionContext.getInstrumentationState() + )); - NonNullableFieldValidator nonNullableFieldValidator = new NonNullableFieldValidator(executionContext, executionStepInfo); + NonNullableFieldValidator nonNullableFieldValidator = new NonNullableFieldValidator(executionContext, executionStepInfo); - ExecutionStrategyParameters newParameters = parameters.transform(builder -> - builder.executionStepInfo(executionStepInfo) - .source(fetchedValue.getFetchedValue()) - .localContext(fetchedValue.getLocalContext()) - .nonNullFieldValidator(nonNullableFieldValidator) - ); + ExecutionStrategyParameters newParameters = parameters.transform(builder -> + builder.executionStepInfo(executionStepInfo) + .source(fetchedValue.getFetchedValue()) + .localContext(fetchedValue.getLocalContext()) + .nonNullFieldValidator(nonNullableFieldValidator) + ); - FieldValueInfo fieldValueInfo = completeValue(executionContext, newParameters); + FieldValueInfo fieldValueInfo = completeValue(executionContext, newParameters); - CompletableFuture executionResultFuture = fieldValueInfo.getFieldValueFuture(); - ctxCompleteField.onDispatched(); - executionResultFuture.whenComplete(ctxCompleteField::onCompleted); - return fieldValueInfo; - }); + CompletableFuture executionResultFuture = fieldValueInfo.getFieldValueFuture(); + ctxCompleteField.onDispatched(); + executionResultFuture.whenComplete(ctxCompleteField::onCompleted); + return fieldValueInfo; } /** @@ -829,15 +811,13 @@ protected FieldValueInfo completeValueForList(ExecutionContext executionContext, overallResult.whenComplete(completeListCtx::onCompleted); resultsFuture.whenComplete((results, exception) -> { - executionContext.run(exception, () -> { - if (exception != null) { - handleValueException(overallResult, exception, executionContext); - return; - } - List completedResults = new ArrayList<>(results.size()); - completedResults.addAll(results); - overallResult.complete(completedResults); - }); + if (exception != null) { + handleValueException(overallResult, exception, executionContext); + return; + } + List completedResults = new ArrayList<>(results.size()); + completedResults.addAll(results); + overallResult.complete(completedResults); }); listOrPromiseToList = overallResult; } else { diff --git a/src/main/java/graphql/execution/SubscriptionExecutionStrategy.java b/src/main/java/graphql/execution/SubscriptionExecutionStrategy.java index 3ce02d390..464f72594 100644 --- a/src/main/java/graphql/execution/SubscriptionExecutionStrategy.java +++ b/src/main/java/graphql/execution/SubscriptionExecutionStrategy.java @@ -56,36 +56,34 @@ public SubscriptionExecutionStrategy(DataFetcherExceptionHandler dataFetcherExce @Override public CompletableFuture execute(ExecutionContext executionContext, ExecutionStrategyParameters parameters) throws NonNullableFieldWasNullException { - return executionContext.call(() -> { - Instrumentation instrumentation = executionContext.getInstrumentation(); - InstrumentationExecutionStrategyParameters instrumentationParameters = new InstrumentationExecutionStrategyParameters(executionContext, parameters); - ExecutionStrategyInstrumentationContext executionStrategyCtx = ExecutionStrategyInstrumentationContext.nonNullCtx(instrumentation.beginExecutionStrategy( - instrumentationParameters, - executionContext.getInstrumentationState() - )); - - CompletableFuture> sourceEventStream = createSourceEventStream(executionContext, parameters); - - // - // when the upstream source event stream completes, subscribe to it and wire in our adapter - CompletableFuture overallResult = sourceEventStream.thenApply((publisher) -> - executionContext.call(() -> { - if (publisher == null) { - ExecutionResultImpl executionResult = new ExecutionResultImpl(null, executionContext.getErrors()); - return executionResult; - } - Function> mapperFunction = eventPayload -> executeSubscriptionEvent(executionContext, parameters, eventPayload); - boolean keepOrdered = keepOrdered(executionContext.getGraphQLContext()); - SubscriptionPublisher mapSourceToResponse = new SubscriptionPublisher(publisher, mapperFunction, keepOrdered); - ExecutionResultImpl executionResult = new ExecutionResultImpl(mapSourceToResponse, executionContext.getErrors()); - return executionResult; - })); - - // dispatched the subscription query - executionStrategyCtx.onDispatched(); - overallResult.whenComplete(executionStrategyCtx::onCompleted); - return overallResult; + Instrumentation instrumentation = executionContext.getInstrumentation(); + InstrumentationExecutionStrategyParameters instrumentationParameters = new InstrumentationExecutionStrategyParameters(executionContext, parameters); + ExecutionStrategyInstrumentationContext executionStrategyCtx = ExecutionStrategyInstrumentationContext.nonNullCtx(instrumentation.beginExecutionStrategy( + instrumentationParameters, + executionContext.getInstrumentationState() + )); + + CompletableFuture> sourceEventStream = createSourceEventStream(executionContext, parameters); + + // + // when the upstream source event stream completes, subscribe to it and wire in our adapter + CompletableFuture overallResult = sourceEventStream.thenApply((publisher) -> + { + if (publisher == null) { + ExecutionResultImpl executionResult = new ExecutionResultImpl(null, executionContext.getErrors()); + return executionResult; + } + Function> mapperFunction = eventPayload -> executeSubscriptionEvent(executionContext, parameters, eventPayload); + boolean keepOrdered = keepOrdered(executionContext.getGraphQLContext()); + SubscriptionPublisher mapSourceToResponse = new SubscriptionPublisher(publisher, mapperFunction, keepOrdered); + ExecutionResultImpl executionResult = new ExecutionResultImpl(mapSourceToResponse, executionContext.getErrors()); + return executionResult; }); + + // dispatched the subscription query + executionStrategyCtx.onDispatched(); + overallResult.whenComplete(executionStrategyCtx::onCompleted); + return overallResult; } private boolean keepOrdered(GraphQLContext graphQLContext) { @@ -111,14 +109,14 @@ private CompletableFuture> createSourceEventStream(ExecutionCo ExecutionStrategyParameters newParameters = firstFieldOfSubscriptionSelection(parameters); CompletableFuture fieldFetched = Async.toCompletableFuture(fetchField(executionContext, newParameters)); - return fieldFetched.thenApply(fetchedValue -> executionContext.call(() -> { + return fieldFetched.thenApply(fetchedValue -> { Object publisher = fetchedValue.getFetchedValue(); if (publisher != null) { assertTrue(publisher instanceof Publisher, () -> "Your data fetcher must return a Publisher of events when using graphql subscriptions"); } //noinspection unchecked,DataFlowIssue return (Publisher) publisher; - })); + }); } /* @@ -135,39 +133,37 @@ private CompletableFuture> createSourceEventStream(ExecutionCo */ private CompletableFuture executeSubscriptionEvent(ExecutionContext executionContext, ExecutionStrategyParameters parameters, Object eventPayload) { - return executionContext.call(() -> { - Instrumentation instrumentation = executionContext.getInstrumentation(); - - ExecutionContext newExecutionContext = executionContext.transform(builder -> builder - .root(eventPayload) - .resetErrors() - ); - ExecutionStrategyParameters newParameters = firstFieldOfSubscriptionSelection(parameters); - ExecutionStepInfo subscribedFieldStepInfo = createSubscribedFieldStepInfo(executionContext, newParameters); - - InstrumentationFieldParameters i13nFieldParameters = new InstrumentationFieldParameters(executionContext, () -> subscribedFieldStepInfo); - InstrumentationContext subscribedFieldCtx = nonNullCtx(instrumentation.beginSubscribedFieldEvent( - i13nFieldParameters, executionContext.getInstrumentationState() - )); - - FetchedValue fetchedValue = unboxPossibleDataFetcherResult(newExecutionContext, parameters, eventPayload); - FieldValueInfo fieldValueInfo = completeField(newExecutionContext, newParameters, fetchedValue); - CompletableFuture overallResult = fieldValueInfo - .getFieldValueFuture() - .thenApply(val -> new ExecutionResultImpl(val, newExecutionContext.getErrors())) - .thenApply(executionResult -> wrapWithRootFieldName(newParameters, executionResult)); - - // dispatch instrumentation so they can know about each subscription event - subscribedFieldCtx.onDispatched(); - overallResult.whenComplete(subscribedFieldCtx::onCompleted); - - // allow them to instrument each ER should they want to - InstrumentationExecutionParameters i13nExecutionParameters = new InstrumentationExecutionParameters( - executionContext.getExecutionInput(), executionContext.getGraphQLSchema()); - - overallResult = overallResult.thenCompose(executionResult -> instrumentation.instrumentExecutionResult(executionResult, i13nExecutionParameters, executionContext.getInstrumentationState())); - return overallResult; - }); + Instrumentation instrumentation = executionContext.getInstrumentation(); + + ExecutionContext newExecutionContext = executionContext.transform(builder -> builder + .root(eventPayload) + .resetErrors() + ); + ExecutionStrategyParameters newParameters = firstFieldOfSubscriptionSelection(parameters); + ExecutionStepInfo subscribedFieldStepInfo = createSubscribedFieldStepInfo(executionContext, newParameters); + + InstrumentationFieldParameters i13nFieldParameters = new InstrumentationFieldParameters(executionContext, () -> subscribedFieldStepInfo); + InstrumentationContext subscribedFieldCtx = nonNullCtx(instrumentation.beginSubscribedFieldEvent( + i13nFieldParameters, executionContext.getInstrumentationState() + )); + + FetchedValue fetchedValue = unboxPossibleDataFetcherResult(newExecutionContext, parameters, eventPayload); + FieldValueInfo fieldValueInfo = completeField(newExecutionContext, newParameters, fetchedValue); + CompletableFuture overallResult = fieldValueInfo + .getFieldValueFuture() + .thenApply(val -> new ExecutionResultImpl(val, newExecutionContext.getErrors())) + .thenApply(executionResult -> wrapWithRootFieldName(newParameters, executionResult)); + + // dispatch instrumentation so they can know about each subscription event + subscribedFieldCtx.onDispatched(); + overallResult.whenComplete(subscribedFieldCtx::onCompleted); + + // allow them to instrument each ER should they want to + InstrumentationExecutionParameters i13nExecutionParameters = new InstrumentationExecutionParameters( + executionContext.getExecutionInput(), executionContext.getGraphQLSchema()); + + overallResult = overallResult.thenCompose(executionResult -> instrumentation.instrumentExecutionResult(executionResult, i13nExecutionParameters, executionContext.getInstrumentationState())); + return overallResult; } private ExecutionResult wrapWithRootFieldName(ExecutionStrategyParameters parameters, ExecutionResult executionResult) { diff --git a/src/test/groovy/graphql/EngineRunningTest.groovy b/src/test/groovy/graphql/EngineRunningTest.groovy index b974b17e5..46104653d 100644 --- a/src/test/groovy/graphql/EngineRunningTest.groovy +++ b/src/test/groovy/graphql/EngineRunningTest.groovy @@ -4,12 +4,20 @@ import graphql.execution.DataFetcherExceptionHandler import graphql.execution.DataFetcherExceptionHandlerResult import graphql.execution.EngineRunningObserver import graphql.execution.ExecutionId +import graphql.execution.instrumentation.Instrumentation +import graphql.execution.instrumentation.InstrumentationState +import graphql.execution.instrumentation.parameters.InstrumentationCreateStateParameters +import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters +import graphql.execution.preparsed.PreparsedDocumentEntry +import graphql.execution.preparsed.PreparsedDocumentProvider +import graphql.parser.Parser import graphql.schema.DataFetcher import spock.lang.Specification import java.util.concurrent.CompletableFuture import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.locks.ReentrantLock +import java.util.function.Function import static graphql.ExecutionInput.newExecutionInput import static graphql.execution.EngineRunningObserver.ENGINE_RUNNING_OBSERVER_KEY @@ -29,6 +37,140 @@ class EngineRunningTest extends Specification { states } + def "preparsed async document provider"() { + given: + def sdl = ''' + + type Query { + hello: String + } + ''' + def df = { env -> + return "world" + } as DataFetcher + def fetchers = ["Query": ["hello": df]] + def schema = TestUtil.schema(sdl, fetchers) + + def query = "{ hello }" + def document = Parser.parse(query) + + CompletableFuture cf = new CompletableFuture() + PreparsedDocumentProvider preparsedDocumentProvider = new PreparsedDocumentProvider() { + @Override + CompletableFuture getDocumentAsync(ExecutionInput executionInput, Function parseAndValidateFunction) { + return cf + } + } + def graphQL = GraphQL.newGraphQL(schema).preparsedDocumentProvider(preparsedDocumentProvider).build() + + def ei = newExecutionInput(query).build() + + List states = trackStates(ei) + + when: + def er = graphQL.executeAsync(ei) + then: + states == [RUNNING, NOT_RUNNING] + + when: + states.clear() + cf.complete(new PreparsedDocumentEntry(document)) + then: + states == [RUNNING, NOT_RUNNING] + er.get().data == [hello: "world"] + + + } + + def "engine starts before instrumentation state and handles async state correctly"() { + given: + def sdl = ''' + + type Query { + hello: String + } + ''' + def df = { env -> + return "world" + } as DataFetcher + def fetchers = ["Query": ["hello": df]] + def schema = TestUtil.schema(sdl, fetchers) + + CompletableFuture cf = new CompletableFuture() + Instrumentation instrumentation = new Instrumentation() { + + @Override + CompletableFuture createStateAsync(InstrumentationCreateStateParameters parameters) { + return cf + } + } + def graphQL = GraphQL.newGraphQL(schema).instrumentation(instrumentation).build() + + def query = "{ hello }" + def ei = newExecutionInput(query).build() + + List states = trackStates(ei) + + when: + def er = graphQL.executeAsync(ei) + then: + states == [RUNNING, NOT_RUNNING] + + when: + states.clear() + cf.complete(new InstrumentationState() {}) + then: + states == [RUNNING, NOT_RUNNING] + er.get().data == [hello: "world"] + + + } + + def "async instrument execution result"() { + given: + def sdl = ''' + + type Query { + hello: String + } + ''' + def df = { env -> + return "world" + } as DataFetcher + def fetchers = ["Query": ["hello": df]] + def schema = TestUtil.schema(sdl, fetchers) + + CompletableFuture cf = new CompletableFuture() + Instrumentation instrumentation = new Instrumentation() { + + @Override + CompletableFuture instrumentExecutionResult(ExecutionResult executionResult, InstrumentationExecutionParameters parameters, InstrumentationState state) { + return cf + } + } + def graphQL = GraphQL.newGraphQL(schema).instrumentation(instrumentation).build() + + def query = "{ hello }" + def ei = newExecutionInput(query).build() + + List states = trackStates(ei) + + when: + def er = graphQL.executeAsync(ei) + then: + states == [RUNNING, NOT_RUNNING] + + when: + states.clear() + cf.complete(ExecutionResultImpl.newExecutionResult().data([hello: "world-modified"]).build()) + then: + er.get().data == [hello: "world-modified"] + states == [RUNNING, NOT_RUNNING] + + + } + + def "engine running state is observed"() { given: def sdl = ''' @@ -61,24 +203,47 @@ class EngineRunningTest extends Specification { def sdl = ''' type Query { - hello: String + hello: String hello2: String + foo: Foo + someStaticValue: Bar + } + type Foo { + name: String + } + type Bar { + staticValue: String } ''' CompletableFuture cf1 = new CompletableFuture(); + CompletableFuture cf2 = new CompletableFuture(); + CompletableFuture cfFooName = new CompletableFuture(); def df1 = { env -> return cf1; } as DataFetcher - CompletableFuture cf2 = new CompletableFuture(); + def df2 = { env -> return cf2; } as DataFetcher - def fetchers = ["Query": ["hello": df1, "hello2": df2]] + def dfFoo = { env -> + return "Foo"; + } as DataFetcher + + def dfFooName = { env -> + return cfFooName; + } as DataFetcher + + def dfStaticValue = { env -> + return [staticValue: "staticValue"] + } as DataFetcher + + + def fetchers = [Query: ["hello": df1, "hello2": df2, foo: dfFoo, someStaticValue: dfStaticValue], Foo: [name: dfFooName]] def schema = TestUtil.schema(sdl, fetchers) def graphQL = GraphQL.newGraphQL(schema).build() - def query = "{ hello hello2 }" + def query = "{ hello hello2 foo { name } someStaticValue {staticValue} }" def ei = newExecutionInput(query).build() List states = trackStates(ei) @@ -91,17 +256,22 @@ class EngineRunningTest extends Specification { when: states.clear(); cf1.complete("world") + then: + states == [RUNNING, NOT_RUNNING] + when: + states.clear(); + cfFooName.complete("FooName") then: states == [RUNNING, NOT_RUNNING] + when: states.clear() cf2.complete("world2") - then: states == [RUNNING, NOT_RUNNING] - er.get().data == [hello: "world", hello2: "world2"] + er.get().data == [hello: "world", hello2: "world2", foo: [name: "FooName"], someStaticValue: [staticValue: "staticValue"]] } @@ -276,7 +446,7 @@ class EngineRunningTest extends Specification { then: result.errors.collect { it.message } == ["recovered"] // we expect simply going from running to finshed - states == [RUNNING, NOT_RUNNING] + new ArrayList<>(states) == [RUNNING, NOT_RUNNING] } diff --git a/src/test/groovy/graphql/execution/AsyncExecutionStrategyTest.groovy b/src/test/groovy/graphql/execution/AsyncExecutionStrategyTest.groovy index 5da87378c..0b7b5f2a1 100644 --- a/src/test/groovy/graphql/execution/AsyncExecutionStrategyTest.groovy +++ b/src/test/groovy/graphql/execution/AsyncExecutionStrategyTest.groovy @@ -1,5 +1,6 @@ package graphql.execution +import graphql.EngineRunningState import graphql.ErrorType import graphql.ExecutionInput import graphql.ExecutionResult @@ -110,6 +111,7 @@ abstract class AsyncExecutionStrategyTest extends Specification { .graphQLContext(graphqlContextMock) .executionInput(ExecutionInput.newExecutionInput("{}").build()) .locale(Locale.getDefault()) + .engineRunningState(new EngineRunningState()) .build() ExecutionStrategyParameters executionStrategyParameters = ExecutionStrategyParameters .newParameters() @@ -152,6 +154,7 @@ abstract class AsyncExecutionStrategyTest extends Specification { .locale(Locale.getDefault()) .graphQLContext(graphqlContextMock) .executionInput(ExecutionInput.newExecutionInput("{}").build()) + .engineRunningState(new EngineRunningState()) .build() ExecutionStrategyParameters executionStrategyParameters = ExecutionStrategyParameters .newParameters() @@ -195,6 +198,7 @@ abstract class AsyncExecutionStrategyTest extends Specification { .instrumentation(SimplePerformantInstrumentation.INSTANCE) .graphQLContext(graphqlContextMock) .executionInput(ExecutionInput.newExecutionInput("{}").build()) + .engineRunningState(new EngineRunningState()) .locale(Locale.getDefault()) .build() ExecutionStrategyParameters executionStrategyParameters = ExecutionStrategyParameters @@ -239,6 +243,7 @@ abstract class AsyncExecutionStrategyTest extends Specification { .locale(Locale.getDefault()) .graphQLContext(graphqlContextMock) .executionInput(ExecutionInput.newExecutionInput("{}").build()) + .engineRunningState(new EngineRunningState()) .build() ExecutionStrategyParameters executionStrategyParameters = ExecutionStrategyParameters .newParameters() @@ -280,6 +285,7 @@ abstract class AsyncExecutionStrategyTest extends Specification { .graphQLContext(graphqlContextMock) .executionInput(ExecutionInput.newExecutionInput("{}").build()) .locale(Locale.getDefault()) + .engineRunningState(new EngineRunningState()) .instrumentation(new SimplePerformantInstrumentation() { @Override diff --git a/src/test/groovy/graphql/execution/AsyncSerialExecutionStrategyTest.groovy b/src/test/groovy/graphql/execution/AsyncSerialExecutionStrategyTest.groovy index f84fbc701..efb67639d 100644 --- a/src/test/groovy/graphql/execution/AsyncSerialExecutionStrategyTest.groovy +++ b/src/test/groovy/graphql/execution/AsyncSerialExecutionStrategyTest.groovy @@ -1,5 +1,6 @@ package graphql.execution +import graphql.EngineRunningState import graphql.ExecutionInput import graphql.GraphQLContext import graphql.execution.instrumentation.SimplePerformantInstrumentation @@ -108,6 +109,7 @@ class AsyncSerialExecutionStrategyTest extends Specification { .locale(Locale.getDefault()) .graphQLContext(GraphQLContext.getDefault()) .executionInput(ExecutionInput.newExecutionInput("{}").build()) + .engineRunningState(new EngineRunningState()) .build() ExecutionStrategyParameters executionStrategyParameters = ExecutionStrategyParameters .newParameters() @@ -155,6 +157,7 @@ class AsyncSerialExecutionStrategyTest extends Specification { .locale(Locale.getDefault()) .graphQLContext(GraphQLContext.getDefault()) .executionInput(ExecutionInput.newExecutionInput("{}").build()) + .engineRunningState(new EngineRunningState()) .build() ExecutionStrategyParameters executionStrategyParameters = ExecutionStrategyParameters .newParameters() @@ -169,35 +172,35 @@ class AsyncSerialExecutionStrategyTest extends Specification { then: !result.isDone() - 1 * df1.get(_,_,_) >> cf1 - 0 * df2.get(_,_,_) >> cf2 - 0 * df3.get(_,_,_) >> cf3 + 1 * df1.get(_, _, _) >> cf1 + 0 * df2.get(_, _, _) >> cf2 + 0 * df3.get(_, _, _) >> cf3 when: cf1.complete("world1") then: !result.isDone() - 0 * df1.get(_,_,_) >> cf1 - 1 * df2.get(_,_,_) >> cf2 - 0 * df3.get(_,_,_) >> cf3 + 0 * df1.get(_, _, _) >> cf1 + 1 * df2.get(_, _, _) >> cf2 + 0 * df3.get(_, _, _) >> cf3 when: cf2.complete("world2") then: !result.isDone() - 0 * df1.get(_,_,_) >> cf1 - 0 * df2.get(_,_,_) >> cf2 - 1 * df3.get(_,_,_) >> cf3 + 0 * df1.get(_, _, _) >> cf1 + 0 * df2.get(_, _, _) >> cf2 + 1 * df3.get(_, _, _) >> cf3 when: cf3.complete("world3") then: - 0 * df1.get(_,_,_) >> cf1 - 0 * df2.get(_,_,_) >> cf2 - 0 * df3.get(_,_,_) >> cf3 + 0 * df1.get(_, _, _) >> cf1 + 0 * df2.get(_, _, _) >> cf2 + 0 * df3.get(_, _, _) >> cf3 result.isDone() result.get().data == ['hello': 'world1', 'hello2': 'world2', 'hello3': 'world3'] } diff --git a/src/test/groovy/graphql/execution/ExecutionContextBuilderTest.groovy b/src/test/groovy/graphql/execution/ExecutionContextBuilderTest.groovy index 09a9a76eb..2512cac1b 100644 --- a/src/test/groovy/graphql/execution/ExecutionContextBuilderTest.groovy +++ b/src/test/groovy/graphql/execution/ExecutionContextBuilderTest.groovy @@ -1,6 +1,6 @@ package graphql.execution -import graphql.ExecutionInput + import graphql.GraphQLContext import graphql.execution.instrumentation.Instrumentation import graphql.language.Document @@ -11,8 +11,6 @@ import graphql.schema.GraphQLSchema import org.dataloader.DataLoaderRegistry import spock.lang.Specification -import java.util.concurrent.CountDownLatch - class ExecutionContextBuilderTest extends Specification { Instrumentation instrumentation = Mock(Instrumentation) @@ -269,75 +267,4 @@ class ExecutionContextBuilderTest extends Specification { OperationDefinition.Operation.SUBSCRIPTION | false | false | true } - def "can track if its running or not"() { - - when: - def executionContext = new ExecutionContextBuilder() - .instrumentation(instrumentation) - .queryStrategy(queryStrategy) - .mutationStrategy(mutationStrategy) - .subscriptionStrategy(subscriptionStrategy) - .graphQLSchema(schema) - .executionId(executionId) - .graphQLContext(graphQLContext) - .root(root) - .operationDefinition(operation) - .fragmentsByName([MyFragment: fragment]) - .dataLoaderRegistry(dataLoaderRegistry) - .executionInput(ExecutionInput.newExecutionInput("query q { f }").build()) - .operationDefinition(OperationDefinition.newOperationDefinition().operation(OperationDefinition.Operation.QUERY).build()) - .build() - - then: - !executionContext.isRunning() - - when: - CountDownLatch latch = new CountDownLatch(1) - CountDownLatch threadLatch = new CountDownLatch(1) - offThread({ - executionContext.run { - threadLatch.countDown() - println("running on ${Thread.currentThread().name}") - latch.await() - } - }) - threadLatch.await() - - then: - executionContext.isRunning() - - when: - latch.countDown() - Thread.sleep(10) // time for the runnable to exit - - then: - !executionContext.isRunning() - - when: - latch = new CountDownLatch(1) - threadLatch = new CountDownLatch(1) - offThread({ - executionContext.call { - threadLatch.countDown() - println("running on ${Thread.currentThread().name}") - latch.await() - return "x" - } - }) - then: - threadLatch.await() - executionContext.isRunning() - - when: - latch.countDown() - Thread.sleep(10) // time for the call to exit - - then: - !executionContext.isRunning() - } - - def offThread(Runnable runnable) { - new Thread(runnable).start() - return "x" - } } diff --git a/src/test/groovy/graphql/execution/ExecutionStrategyTest.groovy b/src/test/groovy/graphql/execution/ExecutionStrategyTest.groovy index ae83a4299..ca513d5ba 100644 --- a/src/test/groovy/graphql/execution/ExecutionStrategyTest.groovy +++ b/src/test/groovy/graphql/execution/ExecutionStrategyTest.groovy @@ -1,6 +1,7 @@ package graphql.execution import graphql.Assert +import graphql.EngineRunningState import graphql.ExceptionWhileDataFetching import graphql.ExecutionInput import graphql.ExecutionResult @@ -84,6 +85,7 @@ class ExecutionStrategyTest extends Specification { .dataLoaderRegistry(new DataLoaderRegistry()) .locale(Locale.getDefault()) .valueUnboxer(ValueUnboxer.DEFAULT) + .engineRunningState(new EngineRunningState()) new ExecutionContext(builder) } @@ -547,7 +549,7 @@ class ExecutionStrategyTest extends Specification { executionStrategy.resolveFieldWithInfo(executionContext, parameters) then: - 1 * dataFetcher.get(_,_,_) >> { environment = (it[2] as Supplier).get() } + 1 * dataFetcher.get(_, _, _) >> { environment = (it[2] as Supplier).get() } environment.fieldDefinition == fieldDefinition environment.graphQLSchema == schema environment.graphQlContext.get("key") == "context" diff --git a/src/test/groovy/graphql/execution/ExecutionTest.groovy b/src/test/groovy/graphql/execution/ExecutionTest.groovy index 7130beca0..6d207ae1a 100644 --- a/src/test/groovy/graphql/execution/ExecutionTest.groovy +++ b/src/test/groovy/graphql/execution/ExecutionTest.groovy @@ -1,5 +1,6 @@ package graphql.execution +import graphql.EngineRunningState import graphql.ExecutionInput import graphql.ExecutionResult import graphql.ExecutionResultImpl @@ -51,7 +52,7 @@ class ExecutionTest extends Specification { def document = parser.parseDocument(query) when: - execution.execute(document, MutationSchema.schema, ExecutionId.generate(), emptyExecutionInput, instrumentationState) + execution.execute(document, MutationSchema.schema, ExecutionId.generate(), emptyExecutionInput, instrumentationState, new EngineRunningState(emptyExecutionInput)) then: queryStrategy.execute == 1 @@ -71,7 +72,7 @@ class ExecutionTest extends Specification { def document = parser.parseDocument(query) when: - execution.execute(document, MutationSchema.schema, ExecutionId.generate(), emptyExecutionInput, instrumentationState) + execution.execute(document, MutationSchema.schema, ExecutionId.generate(), emptyExecutionInput, instrumentationState, new EngineRunningState(emptyExecutionInput)) then: queryStrategy.execute == 0 @@ -91,7 +92,7 @@ class ExecutionTest extends Specification { def document = parser.parseDocument(query) when: - execution.execute(document, MutationSchema.schema, ExecutionId.generate(), emptyExecutionInput, instrumentationState) + execution.execute(document, MutationSchema.schema, ExecutionId.generate(), emptyExecutionInput, instrumentationState, new EngineRunningState(emptyExecutionInput)) then: queryStrategy.execute == 0 @@ -128,7 +129,7 @@ class ExecutionTest extends Specification { when: - execution.execute(document, MutationSchema.schema, ExecutionId.generate(), emptyExecutionInput, instrumentationState) + execution.execute(document, MutationSchema.schema, ExecutionId.generate(), emptyExecutionInput, instrumentationState, new EngineRunningState(emptyExecutionInput)) then: queryStrategy.execute == 0 diff --git a/src/test/groovy/graphql/execution/instrumentation/fieldvalidation/FieldValidationTest.groovy b/src/test/groovy/graphql/execution/instrumentation/fieldvalidation/FieldValidationTest.groovy index 376c5168f..c3b3f4099 100644 --- a/src/test/groovy/graphql/execution/instrumentation/fieldvalidation/FieldValidationTest.groovy +++ b/src/test/groovy/graphql/execution/instrumentation/fieldvalidation/FieldValidationTest.groovy @@ -1,5 +1,6 @@ package graphql.execution.instrumentation.fieldvalidation +import graphql.EngineRunningState import graphql.ExecutionInput import graphql.ExecutionResult import graphql.GraphQL @@ -12,8 +13,6 @@ import graphql.execution.ExecutionId import graphql.execution.ResultPath import graphql.execution.ValueUnboxer import graphql.execution.instrumentation.ChainedInstrumentation -import graphql.execution.instrumentation.SimplePerformantInstrumentation -import graphql.execution.instrumentation.parameters.InstrumentationCreateStateParameters import spock.lang.Specification import java.util.concurrent.CompletableFuture @@ -310,7 +309,7 @@ class FieldValidationTest extends Specification { def execution = new Execution(strategy, strategy, strategy, instrumentation, ValueUnboxer.DEFAULT, false) def executionInput = ExecutionInput.newExecutionInput().query(query).variables(variables).build() - execution.execute(document, schema, ExecutionId.generate(), executionInput, null) + execution.execute(document, schema, ExecutionId.generate(), executionInput, null, new EngineRunningState()) } def "test graphql from end to end with chained instrumentation"() {