Skip to content

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

Merged
merged 3 commits into from
Aug 15, 2025

Conversation

bbakerman
Copy link
Member

@bbakerman bbakerman commented Aug 12, 2025

The current deferred field Instrumentation is broken - it has no arguments and its context is never called back.

This implements it more properly. It calls beingDeferredField each time a field actually gets started - eg after the main operation has finished and we begin to drain the deferred queue

Copy link
Contributor

github-actions bot commented Aug 12, 2025

Test Results

  325 files   -    650    325 suites   - 650   3m 29s ⏱️ - 7m 7s
5 021 tests  -    345  5 015 ✅  -    346  6 💤 + 1  0 ❌ ±0 
5 110 runs   - 10 217  5 104 ✅  - 10 206  6 💤  - 11  0 ❌ ±0 

Results for commit 215be0b. ± Comparison against base commit 3ba9750.

This pull request removes 541 and adds 172 tests. Note that renamed tests count towards both.
	?

	, expected: combo-\"\\\b\f\n\r\t, #4]
                __schema { types { fields { args { type { name fields { name }}}}}}
                __schema { types { fields { type { name fields { name }}}}}
                __schema { types { inputFields { type { inputFields { name }}}}}
                __schema { types { interfaces { fields { type { interfaces { name } } } } } }
                __schema { types { name} }
                __type(name : "t") { name }
                a1: __schema { types { name} }
                a1: __type(name : "t") { name }
…
graphql.DataFetcherTest ‑ get Boolean property value [fetcher: <graphql.schema.PropertyDataFetcher@5a034157 propertyName=booleanField function=null>, #0]
graphql.DataFetcherTest ‑ get Boolean property value [fetcher: <graphql.schema.SingletonPropertyDataFetcher@4b4ee511>, #1]
graphql.DataFetcherTest ‑ get Boolean property value with get [fetcher: <graphql.schema.PropertyDataFetcher@3762c4fc propertyName=booleanFieldWithGet function=null>, #0]
graphql.DataFetcherTest ‑ get Boolean property value with get [fetcher: <graphql.schema.SingletonPropertyDataFetcher@4b4ee511>, #1]
graphql.DataFetcherTest ‑ get property value [fetcher: <graphql.schema.PropertyDataFetcher@1806bc4c propertyName=property function=null>, #0]
graphql.DataFetcherTest ‑ get property value [fetcher: <graphql.schema.SingletonPropertyDataFetcher@4b4ee511>, #1]
graphql.DataFetcherTest ‑ get public field value as property [fetcher: <graphql.schema.PropertyDataFetcher@1b6924cb propertyName=publicField function=null>, #0]
graphql.DataFetcherTest ‑ get public field value as property [fetcher: <graphql.schema.SingletonPropertyDataFetcher@4b4ee511>, #1]
graphql.ScalarsBooleanTest ‑ parseValue throws exception for invalid input <java.lang.Object@784ca467>
graphql.ScalarsBooleanTest ‑ serialize throws exception for invalid input <java.lang.Object@6acd2b8>
…
This pull request skips 1 test.
graphql.schema.fetching.LambdaFetchingSupportTest ‑ different class loaders induce certain behaviours

♻️ This comment has been updated with latest results.

@bbakerman bbakerman added this to the 25.x breaking changes milestone Aug 12, 2025
@bbakerman bbakerman requested review from andimarek and dondonz August 12, 2025 06:17
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
Copy link
Member Author

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 build the ExecutionStepInfo later and this allows me to do that back in the execution strategy

key -> FpKit.interThreadMemoize(() -> {
CompletableFuture<FieldValueInfo> fieldValueResult = resolveFieldWithInfoFn.apply(executionContext, executionStrategyParameters);
key -> FpKit.interThreadMemoize(resolveDeferredFieldValue(currentField, executionContext, executionStrategyParameters)
)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved most of the code into a method resolveDeferredFieldValue - just because it was a lambda in a lambda

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));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was just wrong and it had not tests

*/
@ExperimentalApi
default InstrumentationContext<Object> beginDeferredField(InstrumentationState state) {
default InstrumentationContext<Object> beginDeferredField(InstrumentationFieldParameters parameters, InstrumentationState state) {
return noOp();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we now have field parameters

@@ -1096,6 +1097,11 @@ protected ExecutionStepInfo createExecutionStepInfo(ExecutionContext executionCo
fieldContainer);
}

private Supplier<ExecutionStepInfo> createExecutionStepInfo(ExecutionContext executionContext, ExecutionStrategyParameters parameters) {
GraphQLFieldDefinition fieldDef = getFieldDef(executionContext, parameters, parameters.getField().getSingleField());
return FpKit.intraThreadMemoize(() -> createExecutionStepInfo(executionContext, parameters, fieldDef, null));
Copy link
Member Author

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

@bbakerman bbakerman merged commit 9c92725 into master Aug 15, 2025
2 checks passed
@dondonz dondonz deleted the fix-deferred-field-instrumentation branch August 15, 2025 06:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants