Describe the bug
GraphQL Batch processing throws a ContextNotActiveException
Expected behavior
As described in https://quarkus.io/guides/microprofile-graphql#batching Batch processing should work =)
Actual behavior
GraphQL Batch processing throws a ContextNotActiveException
To Reproduce
See attachment for simple setup:
TestGraphQLResource.zip
Simple Source relations like the following work as expected:
public Yyy getYyy(@Source Xxx xxx) {
return mapYyy(xxx);
}
public List<Zzz> getZzz(@Source Xxx xxx) {
return mapZzz(xxx);
}
When changing to Batch processing a ContextNotActiveException is thrown.
public List<Yyy> getYyy(@Source List<Xxx> xxxList) {
return xxxList.stream().map(this::mapYyy).collect(Collectors.toList());
}
public List<List<Zzz>> getZzz(@Source List<Xxx> xxxList) {
return xxxList.stream().map(this::mapZzz).collect(Collectors.toList());
}
Configuration
No properties set for GraphQL
Screenshots
2020-11-19 11:29:59,847 ERROR [io.sma.graphql] (ForkJoinPool.commonPool-worker-13) SRGQL012000: Data Fetching Error: java.util.concurrent.CompletionException: java.lang.RuntimeException: javax.enterprise.context.ContextNotActiveException: interface javax.enterprise.context.RequestScoped
at java.base/java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:314)
at java.base/java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:319)
at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1702)
at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1692)
at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:177)
Caused by: java.lang.RuntimeException: javax.enterprise.context.ContextNotActiveException: interface javax.enterprise.context.RequestScoped
at io.smallrye.graphql.execution.batchloader.SourceBatchLoader.doSourceCall(SourceBatchLoader.java:83)
at io.smallrye.graphql.execution.batchloader.SourceBatchLoader.access$000(SourceBatchLoader.java:27)
at io.smallrye.graphql.execution.batchloader.SourceBatchLoader$1.run(SourceBatchLoader.java:63)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at io.smallrye.graphql.execution.batchloader.SourceBatchLoader.invokePrivileged(SourceBatchLoader.java:56)
at io.smallrye.graphql.execution.batchloader.SourceBatchLoader.lambda$load$0(SourceBatchLoader.java:49)
at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1700)
... 6 more
Caused by: javax.enterprise.context.ContextNotActiveException: interface javax.enterprise.context.RequestScoped
at whatever.wherever.TestGraphQLResource_ClientProxy.arc$delegate(TestGraphQLResource_ClientProxy.zig:68)
at whatever.wherever.TestGraphQLResource_ClientProxy.getZzz(TestGraphQLResource_ClientProxy.zig:249)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at io.smallrye.graphql.execution.datafetcher.helper.ReflectionHelper.invoke(ReflectionHelper.java:45)
at io.smallrye.graphql.execution.batchloader.SourceBatchLoader.doSourceCall(SourceBatchLoader.java:80)
... 12 more
Environment (please complete the following information):
Additional context
(Add any other context about the problem here.)
/cc @jmartisk, @phillip-kruger, @sberyozkin
Hi @funcbro , thanks for this. We know about this one. Can you test against 1.10.0 and let me know, or alternatively if you have a project example I can look at ?
Hi @phillip-kruger, just tested the problem in 1.10.0.Final. The Exception is still thrown.
Here is the Test-Project based on the 1.10.0.Final archetype:
microprofile-graphql-quickstart.zip
And here is the test-query:
{
xxx {
id
a
yyyId
yyy {
id
a
}
zzzIdList
zzz {
id
a
}
}
}
Thanks ! I am look at this.
@funcbro For now, remove @RequestScoped and see if that work please. Let me know
@phillip-kruger just a little hint should you be unsure where to look - https://github.com/quarkusio/quarkus/blob/1.10.0.Final/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLExecutionHandler.java#L57 this is where we activate the request context. I think the problem with batching is that the handling is performed asynchronously in different threads which don't have the request context active (it didn't go through this piece of code)?
It has nothing to do with the application containing @RequestScoped or not. We have to activate the request scope always.
Yes, I just wanted to give @funcbro a "workaround". Do you want to do a fix ?
Haven't tried but I think that if the processing is asynchronous then the annotation won't have any effect.
Sure, I can have a look into it.
Removing RequestScoped (even it if worked) is no option in my case. Might work for the test-case though.
Also tried to use ActivateRequestContext annotation which also has no impact
Not sure yet how to fix it.
We are processing a request, the request context is active, but during the processing, we spawn a new thread in the ForkJoin pool (see https://github.com/smallrye/smallrye-graphql/blob/1.0.15/server/implementation/src/main/java/io/smallrye/graphql/spi/datafetcher/DefaultDataFetcherService.java#L54) - and that thread accesses CDI beans, so it needs to have the request context propagated into it - how should we do that?
There is the problem that this is happenning in SmallRye code, so we don't have access to Arc.container().requestContext() - do we need to move this code to Quarkus codebase to be able to use Arc and pass the active context to the new thread? Or switch from ForkJoin pool to an executor managed by SR Context Propagation?
Perhaps @mkouba has some clues...
Isn't it what SmallRye Context Propagation is supposed to solve?
Well, it's not a Quarkus-only problem. If you spawn a new thread you need to make sure the request context is (a) active or (b) propagated. For (a) you can use e.g. the RequestContextController (portable CDI API). For (b) @gsmet is right and the SmallRye Context Propagation is the way to go.
I've started tinkering with the integration of SR Context Propagation into SR GraphQL. Hope to know more next week, it's kind of an uncharted territory for me :)