Skip to content

Commit 27b3116

Browse files
committed
Added support for asyncModeEnabled=true
1 parent e168b74 commit 27b3116

File tree

3 files changed

+60
-7
lines changed

3 files changed

+60
-7
lines changed

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

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import java.util.Map;
3131
import java.util.Objects;
3232
import java.util.Optional;
33+
import java.util.concurrent.CountDownLatch;
3334
import java.util.concurrent.atomic.AtomicReference;
3435
import java.util.function.BiConsumer;
3536
import java.util.function.Consumer;
@@ -291,7 +292,7 @@ public String executeQuery(String query) {
291292

292293
private void doRequestAsync(HttpServletRequest request, HttpServletResponse response, HttpRequestHandler handler) {
293294
if (configuration.isAsyncServletModeEnabled()) {
294-
AsyncContext asyncContext = request.startAsync();
295+
AsyncContext asyncContext = request.startAsync(request, response);
295296
HttpServletRequest asyncRequest = (HttpServletRequest) asyncContext.getRequest();
296297
HttpServletResponse asyncResponse = (HttpServletResponse) asyncContext.getResponse();
297298
new Thread(() -> doRequest(asyncRequest, asyncResponse, handler, asyncContext)).start();
@@ -344,12 +345,22 @@ private void query(GraphQLQueryInvoker queryInvoker, GraphQLObjectMapper graphQL
344345
resp.setContentType(APPLICATION_EVENT_STREAM_UTF8);
345346
resp.setStatus(STATUS_OK);
346347

347-
HttpServletRequest req = invocationInput.getContext().getHttpServletRequest().get();
348-
AsyncContext asyncContext = req.startAsync(req, resp);
349-
asyncContext.setTimeout(60 * 1000);
348+
HttpServletRequest req = invocationInput.getContext().getHttpServletRequest().orElseThrow(IllegalStateException::new);
349+
boolean isInAsyncThread = req.isAsyncStarted();
350+
AsyncContext asyncContext = isInAsyncThread ? req.getAsyncContext() : req.startAsync(req, resp);
351+
asyncContext.setTimeout(configuration.getSubscriptionTimeout());
350352
AtomicReference<Subscription> subscriptionRef = new AtomicReference<>();
351353
asyncContext.addListener(new SubscriptionAsyncListener(subscriptionRef));
352-
((Publisher<ExecutionResult>) result.getData()).subscribe(new ExecutionResultSubscriber(subscriptionRef, asyncContext, graphQLObjectMapper));
354+
ExecutionResultSubscriber subscriber = new ExecutionResultSubscriber(subscriptionRef, asyncContext, graphQLObjectMapper);
355+
((Publisher<ExecutionResult>) result.getData()).subscribe(subscriber);
356+
if (isInAsyncThread) {
357+
// We need to delay the completion of async context until after the subscription has terminated, otherwise the AsyncContext is prematurely closed.
358+
try {
359+
subscriber.await();
360+
} catch (InterruptedException e) {
361+
Thread.currentThread().interrupt();
362+
}
363+
}
353364
}
354365
}
355366

@@ -480,6 +491,7 @@ private static class ExecutionResultSubscriber implements Subscriber<ExecutionRe
480491
private final AtomicReference<Subscription> subscriptionRef;
481492
private final AsyncContext asyncContext;
482493
private final GraphQLObjectMapper graphQLObjectMapper;
494+
private final CountDownLatch completedLatch = new CountDownLatch(1);
483495

484496
public ExecutionResultSubscriber(AtomicReference<Subscription> subscriptionRef, AsyncContext asyncContext, GraphQLObjectMapper graphQLObjectMapper) {
485497
this.subscriptionRef = subscriptionRef;
@@ -507,11 +519,17 @@ public void onNext(ExecutionResult executionResult) {
507519
@Override
508520
public void onError(Throwable t) {
509521
asyncContext.complete();
522+
completedLatch.countDown();
510523
}
511524

512525
@Override
513526
public void onComplete() {
514527
asyncContext.complete();
528+
completedLatch.countDown();
529+
}
530+
531+
public void await() throws InterruptedException {
532+
completedLatch.await();
515533
}
516534
}
517535
}

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public class GraphQLConfiguration {
1313
private GraphQLObjectMapper objectMapper;
1414
private List<GraphQLServletListener> listeners;
1515
private boolean asyncServletModeEnabled;
16+
private long subscriptionTimeout;
1617

1718
public static GraphQLConfiguration.Builder with(GraphQLSchema schema) {
1819
return with(new DefaultGraphQLSchemaProvider(schema));
@@ -26,12 +27,13 @@ public static GraphQLConfiguration.Builder with(GraphQLInvocationInputFactory in
2627
return new Builder(invocationInputFactory);
2728
}
2829

29-
private GraphQLConfiguration(GraphQLInvocationInputFactory invocationInputFactory, GraphQLQueryInvoker queryInvoker, GraphQLObjectMapper objectMapper, List<GraphQLServletListener> listeners, boolean asyncServletModeEnabled) {
30+
private GraphQLConfiguration(GraphQLInvocationInputFactory invocationInputFactory, GraphQLQueryInvoker queryInvoker, GraphQLObjectMapper objectMapper, List<GraphQLServletListener> listeners, boolean asyncServletModeEnabled, long subscriptionTimeout) {
3031
this.invocationInputFactory = invocationInputFactory;
3132
this.queryInvoker = queryInvoker;
3233
this.objectMapper = objectMapper;
3334
this.listeners = listeners;
3435
this.asyncServletModeEnabled = asyncServletModeEnabled;
36+
this.subscriptionTimeout = subscriptionTimeout;
3537
}
3638

3739
public GraphQLInvocationInputFactory getInvocationInputFactory() {
@@ -62,6 +64,10 @@ public boolean remove(GraphQLServletListener listener) {
6264
return listeners.remove(listener);
6365
}
6466

67+
public long getSubscriptionTimeout() {
68+
return subscriptionTimeout;
69+
}
70+
6571
public static class Builder {
6672

6773
private GraphQLInvocationInputFactory.Builder invocationInputFactoryBuilder;
@@ -70,6 +76,7 @@ public static class Builder {
7076
private GraphQLObjectMapper objectMapper = GraphQLObjectMapper.newBuilder().build();
7177
private List<GraphQLServletListener> listeners = new ArrayList<>();
7278
private boolean asyncServletModeEnabled = false;
79+
private long subscriptionTimeout = 0;
7380

7481
private Builder(GraphQLInvocationInputFactory.Builder invocationInputFactoryBuilder) {
7582
this.invocationInputFactoryBuilder = invocationInputFactoryBuilder;
@@ -115,13 +122,19 @@ public Builder with(GraphQLRootObjectBuilder rootObjectBuilder) {
115122
return this;
116123
}
117124

125+
public Builder with(long subscriptionTimeout) {
126+
this.subscriptionTimeout = subscriptionTimeout;
127+
return this;
128+
}
129+
118130
public GraphQLConfiguration build() {
119131
return new GraphQLConfiguration(
120132
this.invocationInputFactory != null ? this.invocationInputFactory : invocationInputFactoryBuilder.build(),
121133
queryInvoker,
122134
objectMapper,
123135
listeners,
124-
asyncServletModeEnabled
136+
asyncServletModeEnabled,
137+
subscriptionTimeout
125138
);
126139
}
127140

src/main/java/graphql/servlet/SimpleGraphQLHttpServlet.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,21 @@ public SimpleGraphQLHttpServlet(GraphQLInvocationInputFactory invocationInputFac
3030
.build();
3131
}
3232

33+
/**
34+
* @deprecated use {@link GraphQLHttpServlet} instead
35+
*/
36+
@Deprecated
37+
public SimpleGraphQLHttpServlet(GraphQLInvocationInputFactory invocationInputFactory, GraphQLQueryInvoker queryInvoker, GraphQLObjectMapper graphQLObjectMapper, List<GraphQLServletListener> listeners, boolean asyncServletMode, long subscriptionTimeout) {
38+
super(listeners);
39+
this.configuration = GraphQLConfiguration.with(invocationInputFactory)
40+
.with(queryInvoker)
41+
.with(graphQLObjectMapper)
42+
.with(listeners != null ? listeners : new ArrayList<>())
43+
.with(asyncServletMode)
44+
.with(subscriptionTimeout)
45+
.build();
46+
}
47+
3348
private SimpleGraphQLHttpServlet(GraphQLConfiguration configuration) {
3449
this.configuration = Objects.requireNonNull(configuration, "configuration is required");
3550
}
@@ -77,6 +92,7 @@ public static class Builder {
7792
private GraphQLObjectMapper graphQLObjectMapper = GraphQLObjectMapper.newBuilder().build();
7893
private List<GraphQLServletListener> listeners;
7994
private boolean asyncServletMode;
95+
private long subscriptionTimeout;
8096

8197
Builder(GraphQLInvocationInputFactory invocationInputFactory) {
8298
this.invocationInputFactory = invocationInputFactory;
@@ -102,13 +118,19 @@ public Builder withListeners(List<GraphQLServletListener> listeners) {
102118
return this;
103119
}
104120

121+
public Builder withSubscriptionTimeout(long subscriptionTimeout) {
122+
this.subscriptionTimeout = subscriptionTimeout;
123+
return this;
124+
}
125+
105126
@Deprecated
106127
public SimpleGraphQLHttpServlet build() {
107128
GraphQLConfiguration configuration = GraphQLConfiguration.with(invocationInputFactory)
108129
.with(queryInvoker)
109130
.with(graphQLObjectMapper)
110131
.with(listeners != null ? listeners : new ArrayList<>())
111132
.with(asyncServletMode)
133+
.with(subscriptionTimeout)
112134
.build();
113135
return new SimpleGraphQLHttpServlet(configuration);
114136
}

0 commit comments

Comments
 (0)