graphql-java-tools
graphql-java-tools copied to clipboard
Kotlin coroutine suspend fun resolver hangs when awaiting a data loader
We're using java-graphql batch loaders (https://www.graphql-java.com/documentation/v15/batching/)
Instead of returning CompletableFuture
s in our resolver methods, I tried using kotlin suspend
functions, like this:
suspend fun loadUsers(env: DataFetchingEnvironment): List<UserGQL> {
val users = Context.getDataLoaders(env)
.usersLoader
.loadMany(listOf(1, 2, 3))
.await()
return users.map { UserGQL(it) }
}
However it seems to be hanging on the await
. I think it's blocking there, before java-graphql called the dispatch
on the registered data loaders (in DataLoaderRegistry).
What would be the best way to solve this? I couldn't really find examples in the documentation or the original pull request (https://github.com/graphql-java-kickstart/graphql-java-tools/pull/201)
Might it be because this example in the graphql-java docs: https://www.graphql-java.com/documentation/v15/batching/
BatchLoader<String, Object> batchLoader = new BatchLoader<String, Object>() {
@Override
public CompletionStage<List<Object>> load(List<String> keys) {
return CompletableFuture.completedFuture(getTheseCharacters(keys));
}
};
DataLoader<String, Object> characterDataLoader = DataLoader.newDataLoader(batchLoader);
// .... later in your data fetcher
DataFetcher dataFetcherThatCallsTheDataLoader = new DataFetcher() {
@Override
public Object get(DataFetchingEnvironment environment) {
//
// Don't DO THIS!
//
return CompletableFuture.supplyAsync(() -> {
String argId = environment.getArgument("id");
DataLoader<String, Object> characterLoader = environment.getDataLoader("characterLoader");
return characterLoader.load(argId);
});
}
};
and with MethodFieldResolver.kt:190
environment.coroutineScope().future(options.coroutineContextProvider.provide()) {
invokeSuspend(source, resolverMethod, args)?.transformWithGenericWrapper(environment)
}
it might be equivalent to the supplyAsync
example the docs tells us not to do?
I think the problem is that you're wrapping the CompletionStage provided by the DataLoader API with the coroutine's CompletableFuture representation created by graphql-java-tools. As you've pointed out already, this disconnects graphql-java from your dataloader, and so it is unable to dispatch it.
My understanding is that .loadMany
will not actually call your data loader method directly - it'll only create a "request to fetch these users at some point", which you need to return in your data fetcher so that graphql-java can orchestrate when to actually call the data loader (maybe there'll be more data fetchers making use of the same data loader - then all those keys will be aggregated by graphql-java and the data loader will only be called once, when graphql-java sees fit).
I haven't worked with dataloader + graphql-java-tools yet, but I think you need something like this
fun loadUsers(env: DataFetchingEnvironment): Any {
return Context.getDataLoaders(env)
.usersLoader
.loadMany(listOf(1, 2, 3))
}
Any updates for that issue? I'm facing the same problem now. Here was discussion connected with dataFetcher link The easiest solution is calling dispatch every time but it not the right choice.
suspend fun loadUsers(env: DataFetchingEnvironment): List<UserGQL> {
val dataLoader = Context.getDataLoaders(env).usersLoader
val users = dataLoader
.loadMany(listOf(1, 2, 3))
dataLoader.dispatch()
return users.await().map { UserGQL(it) }
}
So what is the best option to solve this?