Skip to content

Adding Instrumentation for reactive results and when they finish #4084

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

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

bbakerman
Copy link
Member

@bbakerman bbakerman commented Aug 12, 2025

This adds Insrtrumentation support for reactive results - namely defer Publishers and Subscription Publisher.

When the Publisher is finished (throwable or not error) then the Instrumentation context is considered finished and called.

See #4083 for related code

@bbakerman bbakerman added this to the 25.x breaking changes milestone Aug 12, 2025
*/
@SuppressWarnings("TypeParameterUnusedInFormals")
@PublicApi
public class InstrumentationReactiveResultsParameters {
Copy link
Member Author

Choose a reason for hiding this comment

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

New parameters for the Instrumentation method

@Nullable
default InstrumentationContext<Void> beginReactiveResults(InstrumentationReactiveResultsParameters 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.

Notice how its Void - the end of a Publisher has no result object - it has many objects but there is no actual result at the end

*/
public static <T> Publisher<T> whenPublisherFinishes(Publisher<T> publisher, Consumer<? super Throwable> atTheEndCallback) {
return new AtTheEndPublisher<>(publisher, atTheEndCallback);
}
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 helper allows us to wrap a Publisher so that a callback is made when its finished.

if (keepOrdered) {
mappingPublisher = new CompletionStageMappingOrderedPublisher<>(upstreamPublisher, mapper);
} else {
mappingPublisher = new CompletionStageMappingPublisher<>(upstreamPublisher, mapper);
}
publisher = ReactiveSupport.whenPublisherFinishes(mappingPublisher, whenDone);
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 delegate and invoke the whenDone callback


//
// wrap this Publisher into one that can call us back when the publishing is done either in error or successful
publisher = ReactiveSupport.whenPublisherFinishes(publisher, throwable -> ctx.onCompleted(null, throwable));
Copy link
Member Author

Choose a reason for hiding this comment

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

Same here - we delegate to the original Publisher but the callback here is done when the Publisher is finished

Copy link
Contributor

github-actions bot commented Aug 12, 2025

Test Results

  326 files   -    649    326 suites   - 649   3m 31s ⏱️ - 7m 5s
5 035 tests  -    331  5 029 ✅  -    332  6 💤 + 1  0 ❌ ±0 
5 124 runs   - 10 203  5 118 ✅  - 10 192  6 💤  - 11  0 ❌ ±0 

Results for commit d064c7f. ± Comparison against base commit 3ba9750.

This pull request removes 558 and adds 203 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.AssertTest ‑ assertFalse with different number of error args but false does not throw assertions [toRun: <graphql.AssertTest$__spock_feature_0_21prov0_closure23@2a49fe delegate=graphql.AssertTest@67a056f1 owner=graphql.AssertTest@67a056f1 thisObject=graphql.AssertTest@67a056f1 resolveStrategy=0 directive=0 parameterTypes=[class java.lang.Object] maximumNumberOfParameters=1 bcw=null>, expectedMessage: error arg1, #0]
graphql.AssertTest ‑ assertFalse with different number of error args but false does not throw assertions [toRun: <graphql.AssertTest$__spock_feature_0_21prov0_closure24@66596a88 delegate=graphql.AssertTest@67a056f1 owner=graphql.AssertTest@67a056f1 thisObject=graphql.AssertTest@67a056f1 resolveStrategy=0 directive=0 parameterTypes=[class java.lang.Object] maximumNumberOfParameters=1 bcw=null>, expectedMessage: error arg1 arg2, #1]
graphql.AssertTest ‑ assertFalse with different number of error args but false does not throw assertions [toRun: <graphql.AssertTest$__spock_feature_0_21prov0_closure25@5aae8eb5 delegate=graphql.AssertTest@67a056f1 owner=graphql.AssertTest@67a056f1 thisObject=graphql.AssertTest@67a056f1 resolveStrategy=0 directive=0 parameterTypes=[class java.lang.Object] maximumNumberOfParameters=1 bcw=null>, expectedMessage: error arg1 arg2 arg3, #2]
graphql.AssertTest ‑ assertFalse with different number of error args throws assertions [toRun: <graphql.AssertTest$__spock_feature_0_20prov0_closure20@198ef2ce delegate=graphql.AssertTest@67a056f1 owner=graphql.AssertTest@67a056f1 thisObject=graphql.AssertTest@67a056f1 resolveStrategy=0 directive=0 parameterTypes=[class java.lang.Object] maximumNumberOfParameters=1 bcw=null>, expectedMessage: error arg1, #0]
graphql.AssertTest ‑ assertFalse with different number of error args throws assertions [toRun: <graphql.AssertTest$__spock_feature_0_20prov0_closure21@4cbd03e7 delegate=graphql.AssertTest@67a056f1 owner=graphql.AssertTest@67a056f1 thisObject=graphql.AssertTest@67a056f1 resolveStrategy=0 directive=0 parameterTypes=[class java.lang.Object] maximumNumberOfParameters=1 bcw=null>, expectedMessage: error arg1 arg2, #1]
graphql.AssertTest ‑ assertFalse with different number of error args throws assertions [toRun: <graphql.AssertTest$__spock_feature_0_20prov0_closure22@52fc5eb1 delegate=graphql.AssertTest@67a056f1 owner=graphql.AssertTest@67a056f1 thisObject=graphql.AssertTest@67a056f1 resolveStrategy=0 directive=0 parameterTypes=[class java.lang.Object] maximumNumberOfParameters=1 bcw=null>, expectedMessage: error arg1 arg2 arg3, #2]
graphql.AssertTest ‑ assertNotNull with different number of  error args throws assertions [toRun: <graphql.AssertTest$__spock_feature_0_5prov0_closure4@488b50ec delegate=graphql.AssertTest@67a056f1 owner=graphql.AssertTest@67a056f1 thisObject=graphql.AssertTest@67a056f1 resolveStrategy=0 directive=0 parameterTypes=[class java.lang.Object] maximumNumberOfParameters=1 bcw=null>, expectedMessage: error arg1 arg2, #1]
graphql.AssertTest ‑ assertNotNull with different number of  error args throws assertions [toRun: <graphql.AssertTest$__spock_feature_0_5prov0_closure5@54da32dc delegate=graphql.AssertTest@67a056f1 owner=graphql.AssertTest@67a056f1 thisObject=graphql.AssertTest@67a056f1 resolveStrategy=0 directive=0 parameterTypes=[class java.lang.Object] maximumNumberOfParameters=1 bcw=null>, expectedMessage: error arg1 arg2 arg3, #2]
graphql.AssertTest ‑ assertNotNull with different number of error args with non null does not throw assertions [toRun: <graphql.AssertTest$__spock_feature_0_6prov0_closure6@783ec989 delegate=graphql.AssertTest@67a056f1 owner=graphql.AssertTest@67a056f1 thisObject=graphql.AssertTest@67a056f1 resolveStrategy=0 directive=0 parameterTypes=[class java.lang.Object] maximumNumberOfParameters=1 bcw=null>, expectedMessage: error arg1, #0]
graphql.AssertTest ‑ assertNotNull with different number of error args with non null does not throw assertions [toRun: <graphql.AssertTest$__spock_feature_0_6prov0_closure7@4d33940d delegate=graphql.AssertTest@67a056f1 owner=graphql.AssertTest@67a056f1 thisObject=graphql.AssertTest@67a056f1 resolveStrategy=0 directive=0 parameterTypes=[class java.lang.Object] maximumNumberOfParameters=1 bcw=null>, expectedMessage: error arg1 arg2, #1]
…
This pull request skips 1 test.
graphql.schema.fetching.LambdaFetchingSupportTest ‑ different class loaders induce certain behaviours

♻️ This comment has been updated with latest results.

reactiveCtx.onDispatched();

SubscriptionPublisher mapSourceToResponse = new SubscriptionPublisher(publisher, mapperFunction, keepOrdered,
throwable -> reactiveCtx.onCompleted(null, throwable));
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 pass in the call back here. Why?

We want the SubscriptionPublisher to be the implementation we pass back - we have tests for this.

So I changed SubscriptionPublisher internally to know when its finished


called
throwable.message == "BANG"
}
Copy link
Member Author

Choose a reason for hiding this comment

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

Testing that the new helper works

return t1 == t2
}
return false
}
Copy link
Member Author

Choose a reason for hiding this comment

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

Just to make it easier to check large list of values for ordered things. Helpful with Instrumentation "step" testing

["a"], ["b"], ["c"], ["X"], ["e"], ["f"], ["g"])
then:
!actual

Copy link
Member Author

Choose a reason for hiding this comment

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

Test thats the TestUtil helpers work

])

// last of all it finishes
instrumentation.executionList.last == "end:reactive-results-defer"
Copy link
Member Author

Choose a reason for hiding this comment

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

Test that @defer publisher can tell us when they are finished

])

// last of all it finishes
TestUtil.last(instrumentation.executionList) == "end:reactive-results-defer"
Copy link
Member Author

Choose a reason for hiding this comment

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

Rather than test ALL the instrumentation steps again - we look at sub sections and make sure they are in order

This balances completeness with code readability / maintain ability

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.

1 participant