Skip to content

Commit 927b687

Browse files
committed
implement async execution support
1 parent f3188a8 commit 927b687

26 files changed

+816
-136
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ build
55
classes
66
_site
77
generated-src/
8+
out

src/main/java/graphql/ExecutionResultImpl.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
@Internal
1111
public class ExecutionResultImpl implements ExecutionResult {
1212

13-
private final List<GraphQLError> errors;
1413
private final Object data;
14+
private final List<GraphQLError> errors;
1515
private final transient boolean dataPresent;
1616
private final transient Map<Object, Object> extensions;
1717

@@ -74,4 +74,14 @@ public Map<String, Object> toSpecification() {
7474
}
7575
return result;
7676
}
77+
78+
@Override
79+
public String toString() {
80+
return "ExecutionResultImpl{" +
81+
"data=" + data +
82+
", errors=" + errors +
83+
", dataPresent=" + dataPresent +
84+
", extensions=" + extensions +
85+
'}';
86+
}
7787
}

src/main/java/graphql/GraphQL.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
package graphql;
22

3+
import graphql.execution.AsyncExecutionStrategy;
4+
import graphql.execution.AsyncSerialExecutionStrategy;
35
import graphql.execution.Execution;
46
import graphql.execution.ExecutionId;
57
import graphql.execution.ExecutionIdProvider;
68
import graphql.execution.ExecutionStrategy;
7-
import graphql.execution.SimpleExecutionStrategy;
89
import graphql.execution.instrumentation.Instrumentation;
910
import graphql.execution.instrumentation.InstrumentationContext;
1011
import graphql.execution.instrumentation.InstrumentationState;
@@ -106,9 +107,9 @@ public GraphQL(GraphQLSchema graphQLSchema, ExecutionStrategy queryStrategy, Exe
106107

107108
private GraphQL(GraphQLSchema graphQLSchema, ExecutionStrategy queryStrategy, ExecutionStrategy mutationStrategy, ExecutionStrategy subscriptionStrategy, ExecutionIdProvider idProvider, Instrumentation instrumentation, PreparsedDocumentProvider preparsedDocumentProvider) {
108109
this.graphQLSchema = assertNotNull(graphQLSchema, "queryStrategy must be non null");
109-
this.queryStrategy = queryStrategy != null ? queryStrategy : new SimpleExecutionStrategy();
110-
this.mutationStrategy = mutationStrategy != null ? mutationStrategy : new SimpleExecutionStrategy();
111-
this.subscriptionStrategy = subscriptionStrategy != null ? subscriptionStrategy : new SimpleExecutionStrategy();
110+
this.queryStrategy = queryStrategy != null ? queryStrategy : new AsyncExecutionStrategy();
111+
this.mutationStrategy = mutationStrategy != null ? mutationStrategy : new AsyncSerialExecutionStrategy();
112+
this.subscriptionStrategy = subscriptionStrategy != null ? subscriptionStrategy : new AsyncExecutionStrategy();
112113
this.idProvider = assertNotNull(idProvider, "idProvider must be non null");
113114
this.instrumentation = instrumentation;
114115
this.preparsedDocumentProvider = assertNotNull(preparsedDocumentProvider, "preparsedDocumentProvider must be non null");
@@ -129,9 +130,9 @@ public static Builder newGraphQL(GraphQLSchema graphQLSchema) {
129130
@PublicApi
130131
public static class Builder {
131132
private GraphQLSchema graphQLSchema;
132-
private ExecutionStrategy queryExecutionStrategy = new SimpleExecutionStrategy();
133-
private ExecutionStrategy mutationExecutionStrategy = new SimpleExecutionStrategy();
134-
private ExecutionStrategy subscriptionExecutionStrategy = new SimpleExecutionStrategy();
133+
private ExecutionStrategy queryExecutionStrategy = new AsyncExecutionStrategy();
134+
private ExecutionStrategy mutationExecutionStrategy = new AsyncSerialExecutionStrategy();
135+
private ExecutionStrategy subscriptionExecutionStrategy = new AsyncExecutionStrategy();
135136
private ExecutionIdProvider idProvider = DEFAULT_EXECUTION_ID_PROVIDER;
136137
private Instrumentation instrumentation = NoOpInstrumentation.INSTANCE;
137138
private PreparsedDocumentProvider preparsedDocumentProvider = NoOpPreparsedDocumentProvider.INSTANCE;
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package graphql.execution;
2+
3+
import graphql.ExecutionResult;
4+
import graphql.ExecutionResultImpl;
5+
import graphql.language.Field;
6+
7+
import java.util.ArrayList;
8+
import java.util.LinkedHashMap;
9+
import java.util.List;
10+
import java.util.Map;
11+
import java.util.concurrent.CompletableFuture;
12+
import java.util.concurrent.CompletionException;
13+
import java.util.function.BiConsumer;
14+
15+
/**
16+
* The standard graphql execution strategy that runs fields asynchronously
17+
*/
18+
public class AsyncExecutionStrategy extends ExecutionStrategy {
19+
20+
/**
21+
* The standard graphql execution strategy that runs fields asynchronously
22+
*/
23+
public AsyncExecutionStrategy() {
24+
super(new SimpleDataFetcherExceptionHandler());
25+
}
26+
27+
/**
28+
* Creates a execution strategy that uses the provided exception handler
29+
*
30+
* @param exceptionHandler the exception handler to use
31+
*/
32+
public AsyncExecutionStrategy(DataFetcherExceptionHandler exceptionHandler) {
33+
super(exceptionHandler);
34+
}
35+
36+
@Override
37+
public CompletableFuture<ExecutionResult> execute(ExecutionContext executionContext, ExecutionStrategyParameters parameters) throws NonNullableFieldWasNullException {
38+
39+
Map<String, List<Field>> fields = parameters.fields();
40+
List<String> fieldNames = new ArrayList<>(fields.keySet());
41+
List<CompletableFuture<ExecutionResult>> futures = new ArrayList<>();
42+
for (String fieldName : fieldNames) {
43+
List<Field> currentField = fields.get(fieldName);
44+
45+
ExecutionPath fieldPath = parameters.path().segment(fieldName);
46+
ExecutionStrategyParameters newParameters = parameters
47+
.transform(builder -> builder.field(currentField).path(fieldPath));
48+
49+
CompletableFuture<ExecutionResult> future = resolveField(executionContext, newParameters);
50+
futures.add(future);
51+
}
52+
53+
CompletableFuture<ExecutionResult> result = new CompletableFuture<>();
54+
CompletableFuture
55+
.allOf(futures.toArray(new CompletableFuture[futures.size()]))
56+
.whenComplete(futuresCompleted(executionContext, fieldNames, futures, result));
57+
58+
return result;
59+
}
60+
61+
private BiConsumer<Void, Throwable> futuresCompleted(ExecutionContext executionContext,
62+
List<String> fieldNames,
63+
List<CompletableFuture<ExecutionResult>> futures,
64+
CompletableFuture<ExecutionResult> result) {
65+
return (notUsed1, notUsed2) -> {
66+
Map<String, Object> resolvedValuesByField = new LinkedHashMap<>();
67+
int ix = 0;
68+
for (CompletableFuture<ExecutionResult> future : futures) {
69+
70+
if (future.isCompletedExceptionally()) {
71+
future.whenComplete((Null, e) -> handleException(executionContext, result, e));
72+
return;
73+
}
74+
String fieldName = fieldNames.get(ix++);
75+
ExecutionResult resolvedResult = future.join();
76+
resolvedValuesByField.put(fieldName, resolvedResult.getData());
77+
}
78+
result.complete(new ExecutionResultImpl(resolvedValuesByField, executionContext.getErrors()));
79+
};
80+
}
81+
82+
private void handleException(ExecutionContext executionContext, CompletableFuture<ExecutionResult> result, Throwable e) {
83+
if (e instanceof CompletionException && e.getCause() instanceof NonNullableFieldWasNullException) {
84+
assertNonNullFieldPrecondition((NonNullableFieldWasNullException) e.getCause(), result);
85+
if (!result.isDone()) {
86+
result.complete(new ExecutionResultImpl(null, executionContext.getErrors()));
87+
}
88+
} else {
89+
result.completeExceptionally(e);
90+
}
91+
}
92+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package graphql.execution;
2+
3+
import graphql.ExecutionResult;
4+
import graphql.ExecutionResultImpl;
5+
import graphql.language.Field;
6+
7+
import java.util.ArrayList;
8+
import java.util.LinkedHashMap;
9+
import java.util.List;
10+
import java.util.Map;
11+
import java.util.concurrent.CompletableFuture;
12+
import java.util.concurrent.CompletionException;
13+
14+
public class AsyncSerialExecutionStrategy extends ExecutionStrategy {
15+
16+
public AsyncSerialExecutionStrategy() {
17+
super(new SimpleDataFetcherExceptionHandler());
18+
}
19+
20+
public AsyncSerialExecutionStrategy(DataFetcherExceptionHandler exceptionHandler) {
21+
super(exceptionHandler);
22+
}
23+
24+
@Override
25+
public CompletableFuture<ExecutionResult> execute(ExecutionContext executionContext, ExecutionStrategyParameters parameters) throws NonNullableFieldWasNullException {
26+
27+
CompletableFuture<ExecutionResult> result = new CompletableFuture<>();
28+
resolveNthField(executionContext, parameters, 0, new ArrayList<>(), result);
29+
30+
return result;
31+
}
32+
33+
private void resolveNthField(ExecutionContext executionContext,
34+
ExecutionStrategyParameters parameters,
35+
int index,
36+
List<CompletableFuture<ExecutionResult>> allFutures,
37+
CompletableFuture<ExecutionResult> overallResult) {
38+
Map<String, List<Field>> fields = parameters.fields();
39+
List<String> fieldNames = new ArrayList<>(fields.keySet());
40+
String fieldName = fieldNames.get(index);
41+
42+
List<Field> currentField = fields.get(fieldName);
43+
ExecutionPath fieldPath = parameters.path().segment(fieldName);
44+
ExecutionStrategyParameters newParameters = parameters
45+
.transform(builder -> builder.field(currentField).path(fieldPath));
46+
CompletableFuture<ExecutionResult> future = resolveField(executionContext, newParameters);
47+
future.whenComplete((notUsed1, notUsed2) -> {
48+
allFutures.add(future);
49+
if (index + 1 == fields.size()) {
50+
futuresCompleted(executionContext, fieldNames, allFutures, overallResult);
51+
} else {
52+
resolveNthField(executionContext, parameters, index + 1, allFutures, overallResult);
53+
}
54+
});
55+
}
56+
57+
private void futuresCompleted(ExecutionContext executionContext,
58+
List<String> fieldNames,
59+
List<CompletableFuture<ExecutionResult>> futures,
60+
CompletableFuture<ExecutionResult> result) {
61+
Map<String, Object> resolvedValuesByField = new LinkedHashMap<>();
62+
int ix = 0;
63+
for (CompletableFuture<ExecutionResult> future : futures) {
64+
65+
if (future.isCompletedExceptionally()) {
66+
future.whenComplete((Null, e) -> handleException(executionContext, result, e));
67+
return;
68+
}
69+
String fieldName = fieldNames.get(ix++);
70+
ExecutionResult resolvedResult = future.join();
71+
resolvedValuesByField.put(fieldName, resolvedResult.getData());
72+
}
73+
result.complete(new ExecutionResultImpl(resolvedValuesByField, executionContext.getErrors()));
74+
}
75+
76+
private void handleException(ExecutionContext executionContext, CompletableFuture<ExecutionResult> result, Throwable e) {
77+
if (e instanceof CompletionException && e.getCause() instanceof NonNullableFieldWasNullException) {
78+
assertNonNullFieldPrecondition((NonNullableFieldWasNullException) e.getCause(), result);
79+
if (!result.isDone()) {
80+
result.complete(new ExecutionResultImpl(null, executionContext.getErrors()));
81+
}
82+
} else {
83+
result.completeExceptionally(e);
84+
}
85+
}
86+
87+
}

src/main/java/graphql/execution/DataFetcherExceptionHandlerParameters.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ public class DataFetcherExceptionHandlerParameters {
1717
private final GraphQLFieldDefinition fieldDefinition;
1818
private final Map<String, Object> argumentValues;
1919
private final ExecutionPath path;
20-
private final Exception exception;
20+
private final Throwable exception;
2121

22-
public DataFetcherExceptionHandlerParameters(ExecutionContext executionContext, DataFetchingEnvironment dataFetchingEnvironment, Field field, GraphQLFieldDefinition fieldDefinition, Map<String, Object> argumentValues, ExecutionPath path, Exception exception) {
22+
public DataFetcherExceptionHandlerParameters(ExecutionContext executionContext, DataFetchingEnvironment dataFetchingEnvironment, Field field, GraphQLFieldDefinition fieldDefinition, Map<String, Object> argumentValues, ExecutionPath path, Throwable exception) {
2323
this.executionContext = executionContext;
2424
this.dataFetchingEnvironment = dataFetchingEnvironment;
2525
this.field = field;
@@ -57,7 +57,7 @@ public ExecutionPath getPath() {
5757
return path;
5858
}
5959

60-
public Exception getException() {
60+
public Throwable getException() {
6161
return exception;
6262
}
6363

@@ -68,7 +68,7 @@ public static class Builder {
6868
GraphQLFieldDefinition fieldDefinition;
6969
Map<String, Object> argumentValues;
7070
ExecutionPath path;
71-
Exception exception;
71+
Throwable exception;
7272

7373
private Builder() {
7474
}
@@ -103,7 +103,7 @@ public Builder path(ExecutionPath path) {
103103
return this;
104104
}
105105

106-
public Builder exception(Exception exception) {
106+
public Builder exception(Throwable exception) {
107107
this.exception = exception;
108108
return this;
109109
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ public class Execution {
3939
private final Instrumentation instrumentation;
4040

4141
public Execution(ExecutionStrategy queryStrategy, ExecutionStrategy mutationStrategy, ExecutionStrategy subscriptionStrategy, Instrumentation instrumentation) {
42-
this.queryStrategy = queryStrategy != null ? queryStrategy : new SimpleExecutionStrategy();
43-
this.mutationStrategy = mutationStrategy != null ? mutationStrategy : new SimpleExecutionStrategy();
44-
this.subscriptionStrategy = subscriptionStrategy != null ? subscriptionStrategy : new SimpleExecutionStrategy();
42+
this.queryStrategy = queryStrategy != null ? queryStrategy : new AsyncExecutionStrategy();
43+
this.mutationStrategy = mutationStrategy != null ? mutationStrategy : new AsyncExecutionStrategy();
44+
this.subscriptionStrategy = subscriptionStrategy != null ? subscriptionStrategy : new AsyncExecutionStrategy();
4545
this.instrumentation = instrumentation;
4646
}
4747

0 commit comments

Comments
 (0)