Skip to content

Commit 065e2d6

Browse files
authored
615 execution strategy entry exit instrumentation (graphql-java#618)
* Allows for Instrumentation to be called each time an execution strategy is called * Tweaked test naming * Fixed up new method in Instrumentation
1 parent 2829e36 commit 065e2d6

19 files changed

Lines changed: 480 additions & 15 deletions

‎build.gradle‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ dependencies {
4141
testCompile 'org.eclipse.jetty:jetty-server:9.4.5.v20170502'
4242
testCompile 'org.slf4j:slf4j-simple:' + slf4jVersion
4343
testCompile 'org.awaitility:awaitility-groovy:3.0.0'
44+
testCompile 'com.graphql-java:java-dataloader:1.0.1'
4445

4546
}
4647

‎docs/instrumentation.rst‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ The following is a basic custom ``Instrumentation`` that measures overall execut
7575
return new NoOpInstrumentation.NoOpInstrumentationContext<>();
7676
}
7777
78+
@Override
79+
public InstrumentationContext<CompletableFuture<ExecutionResult>> beginExecutionStrategy(InstrumentationExecutionStrategyParameters parameters) {
80+
return new NoOpInstrumentation.NoOpInstrumentationContext<>();
81+
}
82+
7883
@Override
7984
public InstrumentationContext<ExecutionResult> beginField(InstrumentationFieldParameters parameters) {
8085
return new NoOpInstrumentation.NoOpInstrumentationContext<>();

‎src/main/java/graphql/execution/AsyncExecutionStrategy.java‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package graphql.execution;
22

33
import graphql.ExecutionResult;
4+
import graphql.execution.instrumentation.InstrumentationContext;
5+
import graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters;
46
import graphql.language.Field;
57

68
import java.util.ArrayList;
@@ -32,6 +34,8 @@ public AsyncExecutionStrategy(DataFetcherExceptionHandler exceptionHandler) {
3234
@Override
3335
public CompletableFuture<ExecutionResult> execute(ExecutionContext executionContext, ExecutionStrategyParameters parameters) throws NonNullableFieldWasNullException {
3436

37+
InstrumentationContext<CompletableFuture<ExecutionResult>> executionStrategyCtx = executionContext.getInstrumentation().beginExecutionStrategy(new InstrumentationExecutionStrategyParameters(executionContext));
38+
3539
Map<String, List<Field>> fields = parameters.fields();
3640
List<String> fieldNames = new ArrayList<>(fields.keySet());
3741
List<CompletableFuture<ExecutionResult>> futures = new ArrayList<>();
@@ -48,6 +52,8 @@ public CompletableFuture<ExecutionResult> execute(ExecutionContext executionCont
4852

4953
CompletableFuture<ExecutionResult> overallResult = new CompletableFuture<>();
5054
Async.each(futures).whenComplete(handleResults(executionContext, fieldNames, overallResult));
55+
56+
executionStrategyCtx.onEnd(overallResult,null);
5157
return overallResult;
5258
}
5359

‎src/main/java/graphql/execution/AsyncSerialExecutionStrategy.java‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import graphql.ExecutionResult;
44
import graphql.ExecutionResultImpl;
5+
import graphql.execution.instrumentation.InstrumentationContext;
6+
import graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters;
57
import graphql.language.Field;
68

79
import java.util.ArrayList;
@@ -28,6 +30,7 @@ public AsyncSerialExecutionStrategy(DataFetcherExceptionHandler exceptionHandler
2830
@Override
2931
public CompletableFuture<ExecutionResult> execute(ExecutionContext executionContext, ExecutionStrategyParameters parameters) throws NonNullableFieldWasNullException {
3032

33+
InstrumentationContext<CompletableFuture<ExecutionResult>> executionStrategyCtx = executionContext.getInstrumentation().beginExecutionStrategy(new InstrumentationExecutionStrategyParameters(executionContext));
3134
Map<String, List<Field>> fields = parameters.fields();
3235
List<String> fieldNames = new ArrayList<>(fields.keySet());
3336

@@ -42,6 +45,8 @@ public CompletableFuture<ExecutionResult> execute(ExecutionContext executionCont
4245
CompletableFuture<ExecutionResult> overallResult = new CompletableFuture<>();
4346
BiConsumer<List<ExecutionResult>, Throwable> listThrowableBiConsumer = handleResults(executionContext, fieldNames, overallResult);
4447
resultsFuture.whenComplete(listThrowableBiConsumer);
48+
49+
executionStrategyCtx.onEnd(overallResult,null);
4550
return overallResult;
4651
}
4752

‎src/main/java/graphql/execution/ExecutorServiceExecutionStrategy.java‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import graphql.ExecutionResultImpl;
55
import graphql.GraphQLException;
66
import graphql.PublicApi;
7+
import graphql.execution.instrumentation.InstrumentationContext;
8+
import graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters;
79
import graphql.language.Field;
810

911
import java.util.LinkedHashMap;
@@ -50,6 +52,9 @@ public CompletableFuture<ExecutionResult> execute(final ExecutionContext executi
5052
if (executorService == null)
5153
return new AsyncExecutionStrategy().execute(executionContext, parameters);
5254

55+
56+
InstrumentationContext<CompletableFuture<ExecutionResult>> executionStrategyCtx = executionContext.getInstrumentation().beginExecutionStrategy(new InstrumentationExecutionStrategyParameters(executionContext));
57+
5358
Map<String, List<Field>> fields = parameters.fields();
5459
Map<String, Future<CompletableFuture<ExecutionResult>>> futures = new LinkedHashMap<>();
5560
for (String fieldName : fields.keySet()) {
@@ -79,8 +84,11 @@ public CompletableFuture<ExecutionResult> execute(final ExecutionContext executi
7984
}
8085
results.put(fieldName, executionResult != null ? executionResult.getData() : null);
8186
}
82-
return CompletableFuture.completedFuture(new ExecutionResultImpl(results, executionContext.getErrors()));
87+
CompletableFuture<ExecutionResult> result = CompletableFuture.completedFuture(new ExecutionResultImpl(results, executionContext.getErrors()));
88+
executionStrategyCtx.onEnd(result, null);
89+
return result;
8390
} catch (InterruptedException | ExecutionException e) {
91+
executionStrategyCtx.onEnd(null, e);
8492
throw new GraphQLException(e);
8593
}
8694
}

‎src/main/java/graphql/execution/batched/BatchedExecutionStrategy.java‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
import graphql.execution.FieldCollectorParameters;
1515
import graphql.execution.SimpleDataFetcherExceptionHandler;
1616
import graphql.execution.TypeResolutionParameters;
17+
import graphql.execution.instrumentation.InstrumentationContext;
18+
import graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters;
1719
import graphql.language.Field;
1820
import graphql.schema.DataFetcher;
1921
import graphql.schema.DataFetchingEnvironment;
@@ -76,6 +78,8 @@ public BatchedExecutionStrategy(DataFetcherExceptionHandler dataFetcherException
7678

7779
@Override
7880
public CompletableFuture<ExecutionResult> execute(ExecutionContext executionContext, ExecutionStrategyParameters parameters) {
81+
InstrumentationContext<CompletableFuture<ExecutionResult>> executionStrategyCtx = executionContext.getInstrumentation().beginExecutionStrategy(new InstrumentationExecutionStrategyParameters(executionContext));
82+
7983
GraphQLObjectType type = parameters.typeInfo().castType(GraphQLObjectType.class);
8084

8185
ExecutionNode root = new ExecutionNode(type,
@@ -91,6 +95,8 @@ public CompletableFuture<ExecutionResult> execute(ExecutionContext executionCont
9195
nodes,
9296
root.getFields().keySet().iterator(),
9397
result);
98+
99+
executionStrategyCtx.onEnd(result, null);
94100
return result;
95101
}
96102

‎src/main/java/graphql/execution/instrumentation/ChainedInstrumentation.java‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
import graphql.ExecutionResult;
44
import graphql.execution.Async;
5+
import graphql.PublicApi;
56
import graphql.execution.instrumentation.parameters.InstrumentationDataFetchParameters;
67
import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters;
8+
import graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters;
79
import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters;
810
import graphql.execution.instrumentation.parameters.InstrumentationFieldParameters;
911
import graphql.execution.instrumentation.parameters.InstrumentationValidationParameters;
@@ -28,6 +30,7 @@
2830
*
2931
* @see graphql.execution.instrumentation.Instrumentation
3032
*/
33+
@PublicApi
3134
public class ChainedInstrumentation implements Instrumentation {
3235

3336
// This class is inspired from https://github.com/leangen/graphql-spqr/blob/master/src/main/java/io/leangen/graphql/GraphQLRuntime.java#L80
@@ -88,6 +91,16 @@ public InstrumentationContext<ExecutionResult> beginDataFetch(InstrumentationDat
8891
.collect(toList()));
8992
}
9093

94+
@Override
95+
public InstrumentationContext<CompletableFuture<ExecutionResult>> beginExecutionStrategy(InstrumentationExecutionStrategyParameters parameters) {
96+
return new ChainedInstrumentationContext<>(instrumentations.stream()
97+
.map(instrumentation -> {
98+
InstrumentationState state = getState(instrumentation, parameters.getInstrumentationState());
99+
return instrumentation.beginExecutionStrategy(parameters.withNewState(state));
100+
})
101+
.collect(toList()));
102+
}
103+
91104
@Override
92105
public InstrumentationContext<ExecutionResult> beginField(InstrumentationFieldParameters parameters) {
93106
return new ChainedInstrumentationContext<>(instrumentations.stream()

‎src/main/java/graphql/execution/instrumentation/Instrumentation.java‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import graphql.ExecutionResult;
44
import graphql.execution.instrumentation.parameters.InstrumentationDataFetchParameters;
55
import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters;
6+
import graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters;
67
import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters;
78
import graphql.execution.instrumentation.parameters.InstrumentationFieldParameters;
89
import graphql.execution.instrumentation.parameters.InstrumentationValidationParameters;
@@ -74,6 +75,23 @@ default InstrumentationState createState() {
7475
*/
7576
InstrumentationContext<ExecutionResult> beginDataFetch(InstrumentationDataFetchParameters parameters);
7677

78+
/**
79+
* This is called each time the {@link graphql.execution.ExecutionStrategy} is invoked and when the
80+
* {@link java.util.concurrent.CompletableFuture} has been dispatched for the query fields the
81+
* {@link graphql.execution.instrumentation.InstrumentationContext#onEnd(Object, Throwable)}
82+
* is called.
83+
*
84+
* Note because the execution strategy execution is asynchronous, the query data is not guaranteed to be
85+
* completed when this step finishes. It is however a chance to dispatch side effects that might cause
86+
* asynchronous data fetching code to actually run or attach CompletableFuture handlers onto the result
87+
* via Instrumentation.
88+
*
89+
* @param parameters the parameters to this step
90+
*
91+
* @return a non null {@link InstrumentationContext} object that will be called back when the step ends
92+
*/
93+
InstrumentationContext<CompletableFuture<ExecutionResult>> beginExecutionStrategy(InstrumentationExecutionStrategyParameters parameters);
94+
7795
/**
7896
* This is called just before a field is resolved and when this step finishes the {@link InstrumentationContext#onEnd(Object, Throwable)}
7997
* will be called indicating that the step has finished.
@@ -113,7 +131,7 @@ default DataFetcher<?> instrumentDataFetcher(DataFetcher<?> dataFetcher, Instrum
113131
* This is called to allow instrumentation to instrument the execution result in some way
114132
*
115133
* @param executionResult {@link java.util.concurrent.CompletableFuture} of the result to instrument
116-
* @param parameters the parameters to this step
134+
* @param parameters the parameters to this step
117135
*
118136
* @return a new execution result completable future
119137
*/

‎src/main/java/graphql/execution/instrumentation/NoOpInstrumentation.java‎

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,32 @@
11
package graphql.execution.instrumentation;
22

33
import graphql.ExecutionResult;
4+
import graphql.PublicApi;
45
import graphql.execution.instrumentation.parameters.InstrumentationDataFetchParameters;
56
import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters;
7+
import graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters;
68
import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters;
79
import graphql.execution.instrumentation.parameters.InstrumentationFieldParameters;
810
import graphql.execution.instrumentation.parameters.InstrumentationValidationParameters;
911
import graphql.language.Document;
1012
import graphql.validation.ValidationError;
1113

1214
import java.util.List;
15+
import java.util.concurrent.CompletableFuture;
1316

1417
/**
15-
* Nothing to see or do here ;)
18+
* An implementation of {@link graphql.execution.instrumentation.Instrumentation} that does nothing. It can be used
19+
* as a base for derived classes where you only implement the methods you want to
1620
*/
17-
public final class NoOpInstrumentation implements Instrumentation {
21+
@PublicApi
22+
public class NoOpInstrumentation implements Instrumentation {
1823

24+
/**
25+
* A singleton instance of a {@link graphql.execution.instrumentation.Instrumentation} that does nothing
26+
*/
1927
public static final NoOpInstrumentation INSTANCE = new NoOpInstrumentation();
2028

21-
private NoOpInstrumentation() {
29+
public NoOpInstrumentation() {
2230
}
2331

2432
@Override
@@ -36,6 +44,11 @@ public InstrumentationContext<List<ValidationError>> beginValidation(Instrumenta
3644
return new NoOpInstrumentationContext<>();
3745
}
3846

47+
@Override
48+
public InstrumentationContext<CompletableFuture<ExecutionResult>> beginExecutionStrategy(InstrumentationExecutionStrategyParameters parameters) {
49+
return new NoOpInstrumentationContext<>();
50+
}
51+
3952
@Override
4053
public InstrumentationContext<ExecutionResult> beginDataFetch(InstrumentationDataFetchParameters parameters) {
4154
return new NoOpInstrumentationContext<>();
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package graphql.execution.instrumentation.parameters;
2+
3+
import graphql.execution.ExecutionContext;
4+
import graphql.execution.instrumentation.InstrumentationState;
5+
6+
/**
7+
* Parameters sent to {@link graphql.execution.instrumentation.Instrumentation} methods
8+
*/
9+
public class InstrumentationExecutionStrategyParameters {
10+
11+
private final ExecutionContext executionContext;
12+
private final InstrumentationState instrumentationState;
13+
14+
public InstrumentationExecutionStrategyParameters(ExecutionContext executionContext) {
15+
this(executionContext, executionContext.getInstrumentationState());
16+
}
17+
18+
private InstrumentationExecutionStrategyParameters(ExecutionContext executionContext, InstrumentationState instrumentationState) {
19+
this.executionContext = executionContext;
20+
this.instrumentationState = instrumentationState;
21+
}
22+
23+
/**
24+
* Returns a cloned parameters object with the new state
25+
*
26+
* @param instrumentationState the new state for this parameters object
27+
*
28+
* @return a new parameters object with the new state
29+
*/
30+
public InstrumentationExecutionStrategyParameters withNewState(InstrumentationState instrumentationState) {
31+
return new InstrumentationExecutionStrategyParameters(executionContext, instrumentationState);
32+
}
33+
34+
public ExecutionContext getExecutionContext() {
35+
return executionContext;
36+
}
37+
38+
public <T extends InstrumentationState> T getInstrumentationState() {
39+
//noinspection unchecked
40+
return (T) instrumentationState;
41+
}
42+
}

0 commit comments

Comments
 (0)