-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Fixed deferred support to have proper Instrumentation #4083
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,14 +7,18 @@ | |
| import graphql.ExecutionResultImpl; | ||
| import graphql.Internal; | ||
| import graphql.execution.ExecutionContext; | ||
| import graphql.execution.ExecutionStepInfo; | ||
| import graphql.execution.ExecutionStrategyParameters; | ||
| import graphql.execution.FieldValueInfo; | ||
| import graphql.execution.MergedField; | ||
| import graphql.execution.MergedSelectionSet; | ||
| import graphql.execution.ResultPath; | ||
| import graphql.execution.instrumentation.Instrumentation; | ||
| import graphql.execution.instrumentation.InstrumentationContext; | ||
| import graphql.execution.instrumentation.parameters.InstrumentationFieldParameters; | ||
| import graphql.incremental.IncrementalPayload; | ||
| import graphql.util.FpKit; | ||
| import org.jspecify.annotations.NonNull; | ||
|
|
||
| import java.util.Collections; | ||
| import java.util.HashMap; | ||
|
|
@@ -27,6 +31,8 @@ | |
| import java.util.function.BiFunction; | ||
| import java.util.function.Supplier; | ||
|
|
||
| import static graphql.execution.instrumentation.SimpleInstrumentationContext.nonNullCtx; | ||
|
|
||
| /** | ||
| * The purpose of this class hierarchy is to encapsulate most of the logic for deferring field execution, thus | ||
| * keeping the main execution strategy code clean and focused on the main execution logic. | ||
|
|
@@ -59,16 +65,19 @@ class DeferredExecutionSupportImpl implements DeferredExecutionSupport { | |
| private final ExecutionStrategyParameters parameters; | ||
| private final ExecutionContext executionContext; | ||
| private final BiFunction<ExecutionContext, ExecutionStrategyParameters, CompletableFuture<FieldValueInfo>> resolveFieldWithInfoFn; | ||
| private final BiFunction<ExecutionContext, ExecutionStrategyParameters, Supplier<ExecutionStepInfo>> executionStepInfoFn; | ||
| private final Map<String, Supplier<CompletableFuture<DeferredFragmentCall.FieldWithExecutionResult>>> dfCache = new HashMap<>(); | ||
|
|
||
| public DeferredExecutionSupportImpl( | ||
| MergedSelectionSet mergedSelectionSet, | ||
| ExecutionStrategyParameters parameters, | ||
| ExecutionContext executionContext, | ||
| BiFunction<ExecutionContext, ExecutionStrategyParameters, CompletableFuture<FieldValueInfo>> resolveFieldWithInfoFn | ||
| BiFunction<ExecutionContext, ExecutionStrategyParameters, CompletableFuture<FieldValueInfo>> resolveFieldWithInfoFn, | ||
| BiFunction<ExecutionContext, ExecutionStrategyParameters, Supplier<ExecutionStepInfo>> executionStepInfoFn | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I need to be able to build the ExecutionStepInfo later and this allows me to do that back in the execution strategy |
||
| ) { | ||
| this.executionContext = executionContext; | ||
| this.resolveFieldWithInfoFn = resolveFieldWithInfoFn; | ||
| this.executionStepInfoFn = executionStepInfoFn; | ||
| ImmutableListMultimap.Builder<DeferredExecution, MergedField> deferredExecutionToFieldsBuilder = ImmutableListMultimap.builder(); | ||
| ImmutableSet.Builder<MergedField> deferredFieldsBuilder = ImmutableSet.builder(); | ||
| ImmutableList.Builder<String> nonDeferredFieldNamesBuilder = ImmutableList.builder(); | ||
|
|
@@ -153,37 +162,46 @@ private Supplier<CompletableFuture<DeferredFragmentCall.FieldWithExecutionResult | |
| } | ||
| ); | ||
|
|
||
|
|
||
| Instrumentation instrumentation = executionContext.getInstrumentation(); | ||
|
|
||
| instrumentation.beginDeferredField(executionContext.getInstrumentationState()); | ||
|
|
||
| // todo: handle cached computations | ||
| return dfCache.computeIfAbsent( | ||
| currentField.getResultKey(), | ||
| // The same field can be associated with multiple defer executions, so | ||
| // we memoize the field resolution to avoid multiple calls to the same data fetcher | ||
| key -> FpKit.interThreadMemoize(() -> { | ||
| CompletableFuture<FieldValueInfo> fieldValueResult = resolveFieldWithInfoFn.apply(executionContext, executionStrategyParameters); | ||
| key -> FpKit.interThreadMemoize(resolveDeferredFieldValue(currentField, executionContext, executionStrategyParameters) | ||
| ) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. moved most of the code into a method |
||
| ); | ||
| } | ||
|
|
||
| fieldValueResult.whenComplete((fieldValueInfo, throwable) -> { | ||
| executionContext.getDataLoaderDispatcherStrategy().deferredOnFieldValue(currentField.getResultKey(), fieldValueInfo, throwable, executionStrategyParameters); | ||
| }); | ||
| @NonNull | ||
| private Supplier<CompletableFuture<DeferredFragmentCall.FieldWithExecutionResult>> resolveDeferredFieldValue(MergedField currentField, ExecutionContext executionContext, ExecutionStrategyParameters executionStrategyParameters) { | ||
| return () -> { | ||
|
|
||
| Instrumentation instrumentation = executionContext.getInstrumentation(); | ||
| Supplier<ExecutionStepInfo> executionStepInfo = executionStepInfoFn.apply(executionContext, executionStrategyParameters); | ||
| InstrumentationFieldParameters fieldParameters = new InstrumentationFieldParameters(executionContext, executionStepInfo); | ||
| InstrumentationContext<Object> deferredFieldCtx = nonNullCtx(instrumentation.beginDeferredField(fieldParameters, executionContext.getInstrumentationState())); | ||
|
|
||
| CompletableFuture<ExecutionResult> executionResultCF = fieldValueResult | ||
| .thenCompose(fvi -> fvi | ||
| .getFieldValueFuture() | ||
| .thenApply(fv -> ExecutionResultImpl.newExecutionResult().data(fv).build()) | ||
| ); | ||
| CompletableFuture<FieldValueInfo> fieldValueResult = resolveFieldWithInfoFn.apply(this.executionContext, executionStrategyParameters); | ||
|
|
||
| return executionResultCF | ||
| .thenApply(executionResult -> | ||
| new DeferredFragmentCall.FieldWithExecutionResult(currentField.getResultKey(), executionResult) | ||
| ); | ||
| } | ||
| ) | ||
| ); | ||
| deferredFieldCtx.onDispatched(); | ||
|
|
||
| fieldValueResult.whenComplete((fieldValueInfo, throwable) -> { | ||
| this.executionContext.getDataLoaderDispatcherStrategy().deferredOnFieldValue(currentField.getResultKey(), fieldValueInfo, throwable, executionStrategyParameters); | ||
| deferredFieldCtx.onCompleted(fieldValueInfo, throwable); | ||
| }); | ||
|
|
||
|
|
||
| CompletableFuture<ExecutionResult> executionResultCF = fieldValueResult | ||
| .thenCompose(fvi -> fvi | ||
| .getFieldValueFuture() | ||
| .thenApply(fv -> ExecutionResultImpl.newExecutionResult().data(fv).build()) | ||
| ); | ||
|
|
||
| return executionResultCF | ||
| .thenApply(executionResult -> | ||
| new DeferredFragmentCall.FieldWithExecutionResult(currentField.getResultKey(), executionResult) | ||
| ); | ||
| }; | ||
| } | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -166,11 +166,10 @@ public ExecutionStrategyInstrumentationContext beginExecutionStrategy(Instrument | |
|
|
||
| @ExperimentalApi | ||
| @Override | ||
| public InstrumentationContext<Object> beginDeferredField(InstrumentationState instrumentationState) { | ||
| return new ChainedDeferredExecutionStrategyInstrumentationContext(chainedMapAndDropNulls(instrumentationState, Instrumentation::beginDeferredField)); | ||
| public InstrumentationContext<Object> beginDeferredField(InstrumentationFieldParameters parameters, InstrumentationState state) { | ||
| return chainedCtx(state, (instrumentation, specificState) -> instrumentation.beginDeferredField(parameters, specificState)); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was just wrong and it had not tests |
||
| } | ||
|
|
||
|
|
||
| @Override | ||
| public InstrumentationContext<ExecutionResult> beginSubscribedFieldEvent(InstrumentationFieldParameters parameters, InstrumentationState state) { | ||
| return chainedCtx(state, (instrumentation, specificState) -> instrumentation.beginSubscribedFieldEvent(parameters, specificState)); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -153,12 +153,13 @@ default ExecuteObjectInstrumentationContext beginExecuteObject(InstrumentationEx | |
| * <p> | ||
| * This is an EXPERIMENTAL instrumentation callback. The method signature will definitely change. | ||
| * | ||
| * @param state the state created during the call to {@link #createStateAsync(InstrumentationCreateStateParameters)} | ||
| * @param parameters the parameters to this step | ||
| * @param state the state created during the call to {@link #createStateAsync(InstrumentationCreateStateParameters)} | ||
| * | ||
| * @return a nullable {@link ExecutionStrategyInstrumentationContext} object that will be called back when the step ends (assuming it's not null) | ||
| * @return a nullable {@link InstrumentationContext} object that will be called back when the step ends (assuming it's not null) | ||
| */ | ||
| @ExperimentalApi | ||
| default InstrumentationContext<Object> beginDeferredField(InstrumentationState state) { | ||
| default InstrumentationContext<Object> beginDeferredField(InstrumentationFieldParameters parameters, InstrumentationState state) { | ||
| return noOp(); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we now have field parameters |
||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need to be able to create a ExecutionStepInfo for a field but later