Skip to content

Commit 291cfbf

Browse files
Reverts both merge commits that deleted the old defer stuff.
Revert "Merge pull request #1964 from graphql-java/remove-deferred-2" This reverts commit a0d327d, reversing changes made to 3101f48. Revert "Merge pull request #1961 from graphql-java/remove-deferred-support" This reverts commit 3101f48, reversing changes made to 10eeacc.
1 parent d13b509 commit 291cfbf

38 files changed

Lines changed: 2257 additions & 6 deletions
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package graphql;
2+
3+
import java.util.List;
4+
5+
/**
6+
* Results that come back from @defer fields have an extra path property that tells you where
7+
* that deferred result came in the original query
8+
*/
9+
@PublicApi
10+
public interface DeferredExecutionResult extends ExecutionResult {
11+
12+
/**
13+
* @return the execution path of this deferred result in the original query
14+
*/
15+
List<Object> getPath();
16+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package graphql;
2+
3+
import graphql.execution.ExecutionPath;
4+
5+
import java.util.Collections;
6+
import java.util.LinkedHashMap;
7+
import java.util.List;
8+
import java.util.Map;
9+
10+
import static graphql.Assert.assertNotNull;
11+
12+
/**
13+
* Results that come back from @defer fields have an extra path property that tells you where
14+
* that deferred result came in the original query
15+
*/
16+
@PublicApi
17+
public class DeferredExecutionResultImpl extends ExecutionResultImpl implements DeferredExecutionResult {
18+
19+
private final List<Object> path;
20+
21+
private DeferredExecutionResultImpl(List<Object> path, ExecutionResultImpl executionResult) {
22+
super(executionResult);
23+
this.path = assertNotNull(path);
24+
}
25+
26+
/**
27+
* @return the execution path of this deferred result in the original query
28+
*/
29+
public List<Object> getPath() {
30+
return path;
31+
}
32+
33+
@Override
34+
public Map<String, Object> toSpecification() {
35+
Map<String, Object> map = new LinkedHashMap<>(super.toSpecification());
36+
map.put("path", path);
37+
return map;
38+
}
39+
40+
public static Builder newDeferredExecutionResult() {
41+
return new Builder();
42+
}
43+
44+
public static class Builder {
45+
private List<Object> path = Collections.emptyList();
46+
private ExecutionResultImpl.Builder builder = ExecutionResultImpl.newExecutionResult();
47+
48+
public Builder path(ExecutionPath path) {
49+
this.path = assertNotNull(path).toList();
50+
return this;
51+
}
52+
53+
public Builder from(ExecutionResult executionResult) {
54+
builder.from((ExecutionResultImpl) executionResult);
55+
return this;
56+
}
57+
58+
public Builder addErrors(List<GraphQLError> errors) {
59+
builder.addErrors(errors);
60+
return this;
61+
}
62+
63+
public DeferredExecutionResult build() {
64+
ExecutionResultImpl build = builder.build();
65+
return new DeferredExecutionResultImpl(path, build);
66+
}
67+
}
68+
}

src/main/java/graphql/GraphQL.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,12 @@
9191
@PublicApi
9292
public class GraphQL {
9393

94+
/**
95+
* When @defer directives are used, this is the extension key name used to contain the {@link org.reactivestreams.Publisher}
96+
* of deferred results
97+
*/
98+
public static final String DEFERRED_RESULTS = "deferredResults";
99+
94100
private static final Logger log = LoggerFactory.getLogger(GraphQL.class);
95101
private static final Logger logNotSafe = LogKit.getNotPrivacySafeLogger(GraphQL.class);
96102

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

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,28 @@
11
package graphql.execution;
22

33
import graphql.ExecutionResult;
4+
import graphql.execution.defer.DeferSupport;
5+
import graphql.execution.defer.DeferredCall;
6+
import graphql.execution.defer.DeferredErrorSupport;
7+
import graphql.execution.instrumentation.DeferredFieldInstrumentationContext;
48
import graphql.PublicApi;
59
import graphql.execution.instrumentation.ExecutionStrategyInstrumentationContext;
610
import graphql.execution.instrumentation.Instrumentation;
11+
import graphql.execution.instrumentation.parameters.InstrumentationDeferredFieldParameters;
712
import graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters;
13+
import graphql.schema.GraphQLFieldDefinition;
14+
import graphql.schema.GraphQLObjectType;
815

16+
import java.util.ArrayList;
17+
import java.util.LinkedHashMap;
918
import java.util.List;
19+
import java.util.Map;
1020
import java.util.concurrent.CompletableFuture;
1121
import java.util.function.BiConsumer;
22+
import java.util.function.Supplier;
23+
import java.util.stream.Collectors;
1224

25+
import static graphql.execution.MergedSelectionSet.newMergedSelectionSet;
1326

1427
/**
1528
* The standard graphql execution strategy that runs fields asynchronously non-blocking.
@@ -52,7 +65,14 @@ public CompletableFuture<ExecutionResult> execute(ExecutionContext executionCont
5265
ExecutionStrategyParameters newParameters = parameters
5366
.transform(builder -> builder.field(currentField).path(fieldPath).parent(parameters));
5467

55-
CompletableFuture<FieldValueInfo> future = resolveFieldWithInfo(executionContext, newParameters);
68+
CompletableFuture<FieldValueInfo> future;
69+
70+
if (isDeferred(executionContext, newParameters, currentField)) {
71+
executionStrategyCtx.onDeferredField(currentField);
72+
future = resolveFieldWithInfoToNull(executionContext, newParameters);
73+
} else {
74+
future = resolveFieldWithInfo(executionContext, newParameters);
75+
}
5676
futures.add(future);
5777
}
5878
CompletableFuture<ExecutionResult> overallResult = new CompletableFuture<>();
@@ -83,4 +103,57 @@ public CompletableFuture<ExecutionResult> execute(ExecutionContext executionCont
83103
overallResult.whenComplete(executionStrategyCtx::onCompleted);
84104
return overallResult;
85105
}
106+
107+
private boolean isDeferred(ExecutionContext executionContext, ExecutionStrategyParameters parameters, MergedField currentField) {
108+
DeferSupport deferSupport = executionContext.getDeferSupport();
109+
if (deferSupport.checkForDeferDirective(currentField, executionContext.getVariables())) {
110+
DeferredErrorSupport errorSupport = new DeferredErrorSupport();
111+
112+
// with a deferred field we are really resetting where we execute from, that is from this current field onwards
113+
Map<String, MergedField> fields = new LinkedHashMap<>();
114+
fields.put(currentField.getName(), currentField);
115+
116+
ExecutionStrategyParameters callParameters = parameters.transform(builder ->
117+
{
118+
MergedSelectionSet mergedSelectionSet = newMergedSelectionSet().subFields(fields).build();
119+
builder.deferredErrorSupport(errorSupport)
120+
.field(currentField)
121+
.fields(mergedSelectionSet)
122+
.parent(null) // this is a break in the parent -> child chain - its a new start effectively
123+
.listSize(0)
124+
.currentListIndex(0);
125+
}
126+
);
127+
128+
DeferredCall call = new DeferredCall(parameters.getPath(), deferredExecutionResult(executionContext, callParameters), errorSupport);
129+
deferSupport.enqueue(call);
130+
return true;
131+
}
132+
return false;
133+
}
134+
135+
@SuppressWarnings("FutureReturnValueIgnored")
136+
private Supplier<CompletableFuture<ExecutionResult>> deferredExecutionResult(ExecutionContext executionContext, ExecutionStrategyParameters parameters) {
137+
return () -> {
138+
GraphQLFieldDefinition fieldDef = getFieldDef(executionContext, parameters, parameters.getField().getSingleField());
139+
GraphQLObjectType fieldContainer = (GraphQLObjectType) parameters.getExecutionStepInfo().getUnwrappedNonNullType();
140+
141+
Instrumentation instrumentation = executionContext.getInstrumentation();
142+
DeferredFieldInstrumentationContext fieldCtx = instrumentation.beginDeferredField(
143+
new InstrumentationDeferredFieldParameters(executionContext, parameters, fieldDef, createExecutionStepInfo(executionContext, parameters, fieldDef, fieldContainer))
144+
);
145+
CompletableFuture<ExecutionResult> result = new CompletableFuture<>();
146+
fieldCtx.onDispatched(result);
147+
CompletableFuture<FieldValueInfo> fieldValueInfoFuture = resolveFieldWithInfo(executionContext, parameters);
148+
149+
fieldValueInfoFuture.whenComplete((fieldValueInfo, throwable) -> {
150+
fieldCtx.onFieldValueInfo(fieldValueInfo);
151+
152+
CompletableFuture<ExecutionResult> execResultFuture = fieldValueInfo.getFieldValue();
153+
execResultFuture = execResultFuture.whenComplete(fieldCtx::onCompleted);
154+
Async.copyResults(execResultFuture, result);
155+
});
156+
return result;
157+
};
158+
}
86159
}

src/main/java/graphql/execution/Execution.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package graphql.execution;
22

33

4+
import graphql.DeferredExecutionResult;
45
import graphql.ExecutionInput;
56
import graphql.ExecutionResult;
67
import graphql.ExecutionResultImpl;
8+
import graphql.GraphQL;
79
import graphql.GraphQLContext;
810
import graphql.GraphQLError;
911
import graphql.Internal;
12+
import graphql.execution.defer.DeferSupport;
1013
import graphql.execution.instrumentation.Instrumentation;
1114
import graphql.execution.instrumentation.InstrumentationContext;
1215
import graphql.execution.instrumentation.InstrumentationState;
@@ -22,6 +25,7 @@
2225
import graphql.schema.GraphQLSchema;
2326
import graphql.schema.impl.SchemaUtil;
2427
import graphql.util.LogKit;
28+
import org.reactivestreams.Publisher;
2529
import org.slf4j.Logger;
2630

2731
import java.util.Collections;
@@ -177,7 +181,26 @@ private CompletableFuture<ExecutionResult> executeOperation(ExecutionContext exe
177181
result = result.thenApply(er -> mergeExtensionsBuilderIfPresent(er, graphQLContext));
178182

179183
result = result.whenComplete(executeOperationCtx::onCompleted);
180-
return result;
184+
185+
return deferSupport(executionContext, result);
186+
}
187+
188+
/*
189+
* Adds the deferred publisher if its needed at the end of the query. This is also a good time for the deferred code to start running
190+
*/
191+
private CompletableFuture<ExecutionResult> deferSupport(ExecutionContext executionContext, CompletableFuture<ExecutionResult> result) {
192+
return result.thenApply(er -> {
193+
DeferSupport deferSupport = executionContext.getDeferSupport();
194+
if (deferSupport.isDeferDetected()) {
195+
// we start the rest of the query now to maximize throughput. We have the initial important results
196+
// and now we can start the rest of the calls as early as possible (even before some one subscribes)
197+
Publisher<DeferredExecutionResult> publisher = deferSupport.startDeferredCalls();
198+
return ExecutionResultImpl.newExecutionResult().from(er)
199+
.addExtension(GraphQL.DEFERRED_RESULTS, publisher)
200+
.build();
201+
}
202+
return er;
203+
});
181204
}
182205

183206
private void addExtensionsBuilderNotPresent(GraphQLContext graphQLContext) {

src/main/java/graphql/execution/ExecutionContext.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@
66
import graphql.ExecutionInput;
77
import graphql.GraphQLContext;
88
import graphql.GraphQLError;
9+
import graphql.Internal;
910
import graphql.PublicApi;
1011
import graphql.collect.ImmutableKit;
12+
import graphql.cachecontrol.CacheControl;
13+
import graphql.execution.defer.DeferSupport;
1114
import graphql.execution.instrumentation.Instrumentation;
1215
import graphql.execution.instrumentation.InstrumentationState;
1316
import graphql.language.Document;
@@ -53,6 +56,7 @@ public class ExecutionContext {
5356
private final Set<ResultPath> errorPaths = new HashSet<>();
5457
private final DataLoaderRegistry dataLoaderRegistry;
5558
private final Locale locale;
59+
private final DeferSupport deferSupport = new DeferSupport();
5660
private final ValueUnboxer valueUnboxer;
5761
private final ExecutionInput executionInput;
5862
private final Supplier<ExecutableNormalizedOperation> queryTree;
@@ -255,6 +259,10 @@ public ExecutionStrategy getSubscriptionStrategy() {
255259
return subscriptionStrategy;
256260
}
257261

262+
public DeferSupport getDeferSupport() {
263+
return deferSupport;
264+
}
265+
258266
public ExecutionStrategy getStrategy(OperationDefinition.Operation operation) {
259267
if (operation == OperationDefinition.Operation.MUTATION) {
260268
return getMutationStrategy();

src/main/java/graphql/execution/ExecutionStrategy.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,10 @@ protected <T> CompletableFuture<T> handleFetchingException(
372372
.exception(e)
373373
.build();
374374

375+
// TODO: parameters here is an instance of ExecutionStrategyParameters
376+
// TODO: not sure if this method call goes here, inside the try block below, or in the async method
377+
parameters.deferredErrorSupport().onFetchingException(parameters, e);
378+
375379
try {
376380
return asyncHandleException(dataFetcherExceptionHandler, handlerParameters);
377381
} catch (Exception handlerException) {
@@ -496,6 +500,7 @@ private void handleUnresolvedTypeProblem(ExecutionContext context, ExecutionStra
496500
logNotSafe.warn(error.getMessage(), e);
497501
context.addError(error);
498502

503+
parameters.deferredErrorSupport().onError(error);
499504
}
500505

501506
/**
@@ -708,6 +713,7 @@ private Object handleCoercionProblem(ExecutionContext context, ExecutionStrategy
708713
logNotSafe.warn(error.getMessage(), e);
709714
context.addError(error);
710715

716+
parameters.deferredErrorSupport().onError(error);
711717

712718
return null;
713719
}
@@ -735,6 +741,8 @@ private void handleTypeMismatchProblem(ExecutionContext context, ExecutionStrate
735741
TypeMismatchError error = new TypeMismatchError(parameters.getPath(), parameters.getExecutionStepInfo().getUnwrappedNonNullType());
736742
logNotSafe.warn("{} got {}", error.getMessage(), result.getClass());
737743
context.addError(error);
744+
745+
parameters.deferredErrorSupport().onError(error);
738746
}
739747

740748

src/main/java/graphql/execution/ExecutionStrategyParameters.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import graphql.Assert;
44
import graphql.PublicApi;
5+
import graphql.execution.defer.DeferredErrorSupport;
56

67
import java.util.function.Consumer;
78

@@ -20,6 +21,7 @@ public class ExecutionStrategyParameters {
2021
private final ResultPath path;
2122
private final MergedField currentField;
2223
private final ExecutionStrategyParameters parent;
24+
private final DeferredErrorSupport deferredErrorSupport;
2325

2426
private ExecutionStrategyParameters(ExecutionStepInfo executionStepInfo,
2527
Object source,
@@ -28,7 +30,8 @@ private ExecutionStrategyParameters(ExecutionStepInfo executionStepInfo,
2830
NonNullableFieldValidator nonNullableFieldValidator,
2931
ResultPath path,
3032
MergedField currentField,
31-
ExecutionStrategyParameters parent) {
33+
ExecutionStrategyParameters parent,
34+
DeferredErrorSupport deferredErrorSupport) {
3235

3336
this.executionStepInfo = assertNotNull(executionStepInfo, () -> "executionStepInfo is null");
3437
this.localContext = localContext;
@@ -38,6 +41,7 @@ private ExecutionStrategyParameters(ExecutionStepInfo executionStepInfo,
3841
this.path = path;
3942
this.currentField = currentField;
4043
this.parent = parent;
44+
this.deferredErrorSupport = deferredErrorSupport;
4145
}
4246

4347
public ExecutionStepInfo getExecutionStepInfo() {
@@ -68,6 +72,10 @@ public ExecutionStrategyParameters getParent() {
6872
return parent;
6973
}
7074

75+
public DeferredErrorSupport deferredErrorSupport() {
76+
return deferredErrorSupport;
77+
}
78+
7179
/**
7280
* This returns the current field in its query representations.
7381
*
@@ -106,6 +114,7 @@ public static class Builder {
106114
ResultPath path = ResultPath.rootPath();
107115
MergedField currentField;
108116
ExecutionStrategyParameters parent;
117+
DeferredErrorSupport deferredErrorSupport = new DeferredErrorSupport();
109118

110119
/**
111120
* @see ExecutionStrategyParameters#newParameters()
@@ -123,6 +132,7 @@ private Builder(ExecutionStrategyParameters oldParameters) {
123132
this.fields = oldParameters.fields;
124133
this.nonNullableFieldValidator = oldParameters.nonNullableFieldValidator;
125134
this.currentField = oldParameters.currentField;
135+
this.deferredErrorSupport = oldParameters.deferredErrorSupport;
126136
this.path = oldParameters.path;
127137
this.parent = oldParameters.parent;
128138
}
@@ -172,9 +182,13 @@ public Builder parent(ExecutionStrategyParameters parent) {
172182
return this;
173183
}
174184

185+
public Builder deferredErrorSupport(DeferredErrorSupport deferredErrorSupport) {
186+
this.deferredErrorSupport = deferredErrorSupport;
187+
return this;
188+
}
175189

176190
public ExecutionStrategyParameters build() {
177-
return new ExecutionStrategyParameters(executionStepInfo, source, localContext, fields, nonNullableFieldValidator, path, currentField, parent);
191+
return new ExecutionStrategyParameters(executionStepInfo, source, localContext, fields, nonNullableFieldValidator, path, currentField, parent, deferredErrorSupport);
178192
}
179193
}
180194
}

0 commit comments

Comments
 (0)