Skip to content

Commit 8696a81

Browse files
committed
Use thread pool executor for async queries
1 parent 6aa0828 commit 8696a81

File tree

5 files changed

+81
-7
lines changed

5 files changed

+81
-7
lines changed

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

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

5353
private GraphQLConfiguration configuration;
54-
54+
5555
/**
5656
* @deprecated override {@link #getConfiguration()} instead
5757
*/
@@ -292,7 +292,7 @@ private void doRequestAsync(HttpServletRequest request, HttpServletResponse resp
292292
AsyncContext asyncContext = request.startAsync();
293293
HttpServletRequest asyncRequest = (HttpServletRequest) asyncContext.getRequest();
294294
HttpServletResponse asyncResponse = (HttpServletResponse) asyncContext.getResponse();
295-
new Thread(() -> doRequest(asyncRequest, asyncResponse, handler, asyncContext)).start();
295+
configuration.getAsyncExecutor().execute(() -> doRequest(asyncRequest, asyncResponse, handler, asyncContext));
296296
} else {
297297
doRequest(request, response, handler, null);
298298
}

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

Lines changed: 20 additions & 3 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

1720
public static GraphQLConfiguration.Builder with(GraphQLSchema schema) {
1821
return with(new DefaultGraphQLSchemaProvider(schema));
@@ -26,12 +29,13 @@ public static GraphQLConfiguration.Builder with(GraphQLInvocationInputFactory in
2629
return new Builder(invocationInputFactory);
2730
}
2831

29-
private GraphQLConfiguration(GraphQLInvocationInputFactory invocationInputFactory, GraphQLQueryInvoker queryInvoker, GraphQLObjectMapper objectMapper, List<GraphQLServletListener> listeners, boolean asyncServletModeEnabled) {
32+
private GraphQLConfiguration(GraphQLInvocationInputFactory invocationInputFactory, GraphQLQueryInvoker queryInvoker, GraphQLObjectMapper objectMapper, List<GraphQLServletListener> listeners, boolean asyncServletModeEnabled, Executor asyncExecutor) {
3033
this.invocationInputFactory = invocationInputFactory;
3134
this.queryInvoker = queryInvoker;
3235
this.objectMapper = objectMapper;
3336
this.listeners = listeners;
3437
this.asyncServletModeEnabled = asyncServletModeEnabled;
38+
this.asyncExecutor = asyncExecutor;
3539
}
3640

3741
public GraphQLInvocationInputFactory getInvocationInputFactory() {
@@ -54,6 +58,10 @@ public boolean isAsyncServletModeEnabled() {
5458
return asyncServletModeEnabled;
5559
}
5660

61+
public Executor getAsyncExecutor() {
62+
return asyncExecutor;
63+
}
64+
5765
public void add(GraphQLServletListener listener) {
5866
listeners.add(listener);
5967
}
@@ -70,6 +78,7 @@ public static class Builder {
7078
private GraphQLObjectMapper objectMapper = GraphQLObjectMapper.newBuilder().build();
7179
private List<GraphQLServletListener> listeners = new ArrayList<>();
7280
private boolean asyncServletModeEnabled = false;
81+
private Executor asyncExecutor = Executors.newCachedThreadPool(new GraphQLThreadFactory());
7382

7483
private Builder(GraphQLInvocationInputFactory.Builder invocationInputFactoryBuilder) {
7584
this.invocationInputFactoryBuilder = invocationInputFactoryBuilder;
@@ -105,6 +114,13 @@ public Builder with(boolean asyncServletModeEnabled) {
105114
return this;
106115
}
107116

117+
public Builder with(Executor asyncExecutor) {
118+
if (asyncExecutor != null) {
119+
this.asyncExecutor = asyncExecutor;
120+
}
121+
return this;
122+
}
123+
108124
public Builder with(GraphQLContextBuilder contextBuilder) {
109125
this.invocationInputFactoryBuilder.withGraphQLContextBuilder(contextBuilder);
110126
return this;
@@ -121,7 +137,8 @@ public GraphQLConfiguration build() {
121137
queryInvoker,
122138
objectMapper,
123139
listeners,
124-
asyncServletModeEnabled
140+
asyncServletModeEnabled,
141+
asyncExecutor
125142
);
126143
}
127144

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
@@ -7,6 +7,7 @@ import graphql.execution.ExecutionStepInfo
77
import graphql.execution.instrumentation.ChainedInstrumentation
88

99
import graphql.execution.instrumentation.Instrumentation
10+
import graphql.schema.DataFetcher
1011
import graphql.schema.GraphQLNonNull
1112
import org.dataloader.DataLoaderRegistry
1213
import org.springframework.mock.web.MockHttpServletRequest
@@ -38,6 +39,7 @@ class AbstractGraphQLHttpServletSpec extends Specification {
3839
def setup() {
3940
servlet = TestUtils.createServlet()
4041
request = new MockHttpServletRequest()
42+
request.setAsyncSupported(true)
4143
response = new MockHttpServletResponse()
4244
}
4345

@@ -84,6 +86,18 @@ class AbstractGraphQLHttpServletSpec extends Specification {
8486
getResponseContent().data.echo == "test"
8587
}
8688

89+
def "async query over HTTP GET starts async request"() {
90+
setup:
91+
servlet = TestUtils.createServlet({ env -> env.arguments.arg },{ env -> env.arguments.arg }, true)
92+
request.addParameter('query', 'query { echo(arg:"test") }')
93+
94+
when:
95+
servlet.doGet(request, response)
96+
97+
then:
98+
request.asyncStarted == true
99+
}
100+
87101
def "query over HTTP GET with variables returns data"() {
88102
setup:
89103
request.addParameter('query', 'query Echo($arg: String) { echo(arg:$arg) }')
@@ -286,6 +300,20 @@ class AbstractGraphQLHttpServletSpec extends Specification {
286300
getResponseContent().data.echo == "test"
287301
}
288302

303+
def "async query over HTTP POST starts async request"() {
304+
setup:
305+
servlet = TestUtils.createServlet({ env -> env.arguments.arg },{ env -> env.arguments.arg }, true)
306+
request.setContent(mapper.writeValueAsBytes([
307+
query: 'query { echo(arg:"test") }'
308+
]))
309+
310+
when:
311+
servlet.doPost(request, response)
312+
313+
then:
314+
request.asyncStarted == true
315+
}
316+
289317
def "query over HTTP POST body with graphql contentType returns data"() {
290318
setup:
291319
request.addHeader("Content-Type", "application/graphql")

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@ import graphql.schema.*
88
class TestUtils {
99

1010
static def createServlet(DataFetcher queryDataFetcher = { env -> env.arguments.arg },
11-
DataFetcher mutationDataFetcher = { env -> env.arguments.arg }) {
11+
DataFetcher mutationDataFetcher = { env -> env.arguments.arg },
12+
boolean asyncServletModeEnabled = false) {
1213
GraphQLHttpServlet servlet = GraphQLHttpServlet.with(GraphQLConfiguration
1314
.with(createGraphQlSchema(queryDataFetcher, mutationDataFetcher))
14-
.with(createInstrumentedQueryInvoker()).build())
15+
.with(createInstrumentedQueryInvoker())
16+
.with(asyncServletModeEnabled)
17+
.build())
1518
servlet.init(null)
1619
return servlet
1720
}

0 commit comments

Comments
 (0)