Skip to content

Commit ceed1f3

Browse files
authored
Merge pull request graphql-java-kickstart#146 from jnutting512/master
Use thread pool executor for async queries
2 parents 673e9eb + 43d00d2 commit ceed1f3

File tree

5 files changed

+92
-15
lines changed

5 files changed

+92
-15
lines changed

src/main/java/graphql/servlet/AbstractGraphQLHttpServlet.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public abstract class AbstractGraphQLHttpServlet extends HttpServlet implements
5454
private static final String[] MULTIPART_KEYS = new String[]{"operations", "graphql", "query"};
5555

5656
private GraphQLConfiguration configuration;
57-
57+
5858
/**
5959
* @deprecated override {@link #getConfiguration()} instead
6060
*/
@@ -295,7 +295,7 @@ private void doRequestAsync(HttpServletRequest request, HttpServletResponse resp
295295
AsyncContext asyncContext = request.startAsync(request, response);
296296
HttpServletRequest asyncRequest = (HttpServletRequest) asyncContext.getRequest();
297297
HttpServletResponse asyncResponse = (HttpServletResponse) asyncContext.getResponse();
298-
new Thread(() -> doRequest(asyncRequest, asyncResponse, handler, asyncContext)).start();
298+
configuration.getAsyncExecutor().execute(() -> doRequest(asyncRequest, asyncResponse, handler, asyncContext));
299299
} else {
300300
doRequest(request, response, handler, null);
301301
}

src/main/java/graphql/servlet/GraphQLConfiguration.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package graphql.servlet;
22

33
import graphql.schema.GraphQLSchema;
4+
import graphql.servlet.internal.GraphQLThreadFactory;
45

56
import java.util.ArrayList;
67
import java.util.List;
7-
import java.util.Objects;
8+
import java.util.concurrent.Executor;
9+
import java.util.concurrent.Executors;
810

911
public class GraphQLConfiguration {
1012

@@ -13,6 +15,7 @@ public class GraphQLConfiguration {
1315
private GraphQLObjectMapper objectMapper;
1416
private List<GraphQLServletListener> listeners;
1517
private boolean asyncServletModeEnabled;
18+
private Executor asyncExecutor;
1619
private long subscriptionTimeout;
1720

1821
public static GraphQLConfiguration.Builder with(GraphQLSchema schema) {
@@ -27,12 +30,13 @@ public static GraphQLConfiguration.Builder with(GraphQLInvocationInputFactory in
2730
return new Builder(invocationInputFactory);
2831
}
2932

30-
private GraphQLConfiguration(GraphQLInvocationInputFactory invocationInputFactory, GraphQLQueryInvoker queryInvoker, GraphQLObjectMapper objectMapper, List<GraphQLServletListener> listeners, boolean asyncServletModeEnabled, long subscriptionTimeout) {
33+
private GraphQLConfiguration(GraphQLInvocationInputFactory invocationInputFactory, GraphQLQueryInvoker queryInvoker, GraphQLObjectMapper objectMapper, List<GraphQLServletListener> listeners, boolean asyncServletModeEnabled, Executor asyncExecutor, long subscriptionTimeout) {
3134
this.invocationInputFactory = invocationInputFactory;
3235
this.queryInvoker = queryInvoker;
3336
this.objectMapper = objectMapper;
3437
this.listeners = listeners;
3538
this.asyncServletModeEnabled = asyncServletModeEnabled;
39+
this.asyncExecutor = asyncExecutor;
3640
this.subscriptionTimeout = subscriptionTimeout;
3741
}
3842

@@ -56,6 +60,10 @@ public boolean isAsyncServletModeEnabled() {
5660
return asyncServletModeEnabled;
5761
}
5862

63+
public Executor getAsyncExecutor() {
64+
return asyncExecutor;
65+
}
66+
5967
public void add(GraphQLServletListener listener) {
6068
listeners.add(listener);
6169
}
@@ -76,6 +84,7 @@ public static class Builder {
7684
private GraphQLObjectMapper objectMapper = GraphQLObjectMapper.newBuilder().build();
7785
private List<GraphQLServletListener> listeners = new ArrayList<>();
7886
private boolean asyncServletModeEnabled = false;
87+
private Executor asyncExecutor = Executors.newCachedThreadPool(new GraphQLThreadFactory());
7988
private long subscriptionTimeout = 0;
8089

8190
private Builder(GraphQLInvocationInputFactory.Builder invocationInputFactoryBuilder) {
@@ -112,6 +121,13 @@ public Builder with(boolean asyncServletModeEnabled) {
112121
return this;
113122
}
114123

124+
public Builder with(Executor asyncExecutor) {
125+
if (asyncExecutor != null) {
126+
this.asyncExecutor = asyncExecutor;
127+
}
128+
return this;
129+
}
130+
115131
public Builder with(GraphQLContextBuilder contextBuilder) {
116132
this.invocationInputFactoryBuilder.withGraphQLContextBuilder(contextBuilder);
117133
return this;
@@ -134,6 +150,7 @@ public GraphQLConfiguration build() {
134150
objectMapper,
135151
listeners,
136152
asyncServletModeEnabled,
153+
asyncExecutor,
137154
subscriptionTimeout
138155
);
139156
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package graphql.servlet.internal;
2+
3+
import java.util.concurrent.ThreadFactory;
4+
import java.util.concurrent.atomic.AtomicInteger;
5+
6+
import graphql.servlet.AbstractGraphQLHttpServlet;
7+
8+
/**
9+
* {@link ThreadFactory} implementation for {@link AbstractGraphQLHttpServlet} async operations
10+
*
11+
* @author John Nutting
12+
*/
13+
public class GraphQLThreadFactory implements ThreadFactory {
14+
15+
final static String NAME_PREFIX = "GraphQLServlet-";
16+
final AtomicInteger threadNumber = new AtomicInteger(1);
17+
18+
@Override
19+
public Thread newThread(final Runnable r) {
20+
Thread t = new Thread(r, NAME_PREFIX + threadNumber.getAndIncrement());
21+
t.setDaemon(false);
22+
t.setPriority(Thread.NORM_PRIORITY);
23+
return t;
24+
}
25+
26+
}

src/test/groovy/graphql/servlet/AbstractGraphQLHttpServletSpec.groovy

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import graphql.Scalars
55
import graphql.execution.ExecutionStepInfo
66
import graphql.execution.instrumentation.ChainedInstrumentation
77
import graphql.execution.instrumentation.Instrumentation
8+
import graphql.schema.DataFetcher
89
import graphql.execution.reactive.SingleSubscriberPublisher
910
import graphql.schema.GraphQLNonNull
1011
import org.dataloader.DataLoaderRegistry
@@ -54,6 +55,7 @@ class AbstractGraphQLHttpServletSpec extends Specification {
5455
})
5556

5657
request = new MockHttpServletRequest()
58+
request.setAsyncSupported(true)
5759
request.asyncSupported = true
5860
response = new MockHttpServletResponse()
5961
}
@@ -112,6 +114,18 @@ class AbstractGraphQLHttpServletSpec extends Specification {
112114
getResponseContent().data.echo == "test"
113115
}
114116

117+
def "async query over HTTP GET starts async request"() {
118+
setup:
119+
servlet = TestUtils.createServlet({ env -> env.arguments.arg },{ env -> env.arguments.arg }, true)
120+
request.addParameter('query', 'query { echo(arg:"test") }')
121+
122+
when:
123+
servlet.doGet(request, response)
124+
125+
then:
126+
request.asyncStarted == true
127+
}
128+
115129
def "query over HTTP GET with variables returns data"() {
116130
setup:
117131
request.addParameter('query', 'query Echo($arg: String) { echo(arg:$arg) }')
@@ -334,6 +348,20 @@ class AbstractGraphQLHttpServletSpec extends Specification {
334348
getResponseContent().data.echo == "test"
335349
}
336350

351+
def "async query over HTTP POST starts async request"() {
352+
setup:
353+
servlet = TestUtils.createServlet({ env -> env.arguments.arg },{ env -> env.arguments.arg }, true)
354+
request.setContent(mapper.writeValueAsBytes([
355+
query: 'query { echo(arg:"test") }'
356+
]))
357+
358+
when:
359+
servlet.doPost(request, response)
360+
361+
then:
362+
request.asyncStarted == true
363+
}
364+
337365
def "query over HTTP POST body with graphql contentType returns data"() {
338366
setup:
339367
request.addHeader("Content-Type", "application/graphql")

src/test/groovy/graphql/servlet/TestUtils.groovy

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ import graphql.Scalars
55
import graphql.execution.instrumentation.Instrumentation
66
import graphql.execution.reactive.SingleSubscriberPublisher
77
import graphql.schema.*
8-
import org.reactivestreams.Publisher
98

109
import java.util.concurrent.atomic.AtomicReference
1110

1211
class TestUtils {
1312

1413
static def createServlet(DataFetcher queryDataFetcher = { env -> env.arguments.arg },
1514
DataFetcher mutationDataFetcher = { env -> env.arguments.arg },
15+
boolean asyncServletModeEnabled = false,
1616
DataFetcher subscriptionDataFetcher = { env ->
1717
AtomicReference<SingleSubscriberPublisher<String>> publisherRef = new AtomicReference<>();
1818
publisherRef.set(new SingleSubscriberPublisher<>({ subscription ->
@@ -23,7 +23,9 @@ class TestUtils {
2323
}) {
2424
GraphQLHttpServlet servlet = GraphQLHttpServlet.with(GraphQLConfiguration
2525
.with(createGraphQlSchema(queryDataFetcher, mutationDataFetcher, subscriptionDataFetcher))
26-
.with(createInstrumentedQueryInvoker()).build())
26+
.with(createInstrumentedQueryInvoker())
27+
.with(asyncServletModeEnabled)
28+
.build())
2729
servlet.init(null)
2830
return servlet
2931
}
@@ -72,23 +74,27 @@ class TestUtils {
7274
}
7375
field.dataFetcher(mutationDataFetcher)
7476
}
75-
.field { field ->
77+
.field { field ->
7678
field.name("echoFile")
7779
field.type(Scalars.GraphQLString)
7880
field.argument { argument ->
7981
argument.name("file")
8082
argument.type(ApolloScalars.Upload)
8183
}
82-
field.dataFetcher( { env -> new String(ByteStreams.toByteArray(env.arguments.file.getInputStream())) } )
84+
field.dataFetcher({ env -> new String(ByteStreams.toByteArray(env.arguments.file.getInputStream())) })
8385
}
84-
.field { field ->
86+
.field { field ->
8587
field.name("echoFiles")
8688
field.type(GraphQLList.list(Scalars.GraphQLString))
8789
field.argument { argument ->
8890
argument.name("files")
8991
argument.type(GraphQLList.list(GraphQLNonNull.nonNull(ApolloScalars.Upload)))
9092
}
91-
field.dataFetcher( { env -> env.arguments.files.collect { new String(ByteStreams.toByteArray(it.getInputStream())) } } )
93+
field.dataFetcher({ env ->
94+
env.arguments.files.collect {
95+
new String(ByteStreams.toByteArray(it.getInputStream()))
96+
}
97+
})
9298
}
9399
.build()
94100

@@ -107,11 +113,11 @@ class TestUtils {
107113

108114

109115
return GraphQLSchema.newSchema()
110-
.query(query)
111-
.mutation(mutation)
112-
.subscription(subscription)
113-
.additionalType(ApolloScalars.Upload)
114-
.build()
116+
.query(query)
117+
.mutation(mutation)
118+
.subscription(subscription)
119+
.additionalType(ApolloScalars.Upload)
120+
.build()
115121
}
116122

117123
}

0 commit comments

Comments
 (0)