Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Coroutine support for @GraphQlExceptionHandler #750

Closed
juliuskrah opened this issue Jul 15, 2023 · 0 comments
Closed

Coroutine support for @GraphQlExceptionHandler #750

juliuskrah opened this issue Jul 15, 2023 · 0 comments
Assignees
Labels
type: enhancement A general enhancement
Milestone

Comments

@juliuskrah
Copy link

juliuskrah commented Jul 15, 2023

I have implemented a @GraphQlExceptionHandler as a coroutine which isn't working as expected.

Details of my setup

  • java-version: 17
  • kotlin-version: 1.8.22
  • spring-boot: 3.1.1
  • windows: 11

Details of implementation

When resolving an exception, the framework runs into the following exception:

2023-07-16T00:19:36.161+02:00  WARN 15736 --- [ctor-http-nio-2] a.s.AnnotatedControllerExceptionResolver : Failure while handling exception with public java.lang.Object com.example.graph.NodeController.handle(java.lang.Throwable,graphql.schema.DataFetchingEnvironment,kotlin.coroutines.Continuation<? super graphql.GraphQLError>)

java.lang.ClassCastException: class reactor.core.publisher.MonoOnErrorResume cannot be cast to class graphql.GraphQLError (reactor.core.publisher.MonoOnErrorResume and graphql.GraphQLError are in unnamed module of loader 'app')
	at org.springframework.graphql.data.method.annotation.support.AnnotatedControllerExceptionResolver$ReturnValueAdapter.lambda$static$1(AnnotatedControllerExceptionResolver.java:412) ~[spring-graphql-1.2.1.jar:1.2.1]
	at org.springframework.graphql.data.method.annotation.support.AnnotatedControllerExceptionResolver$MethodHolder.adapt(AnnotatedControllerExceptionResolver.java:341) ~[spring-graphql-1.2.1.jar:1.2.1]
	at org.springframework.graphql.data.method.annotation.support.AnnotatedControllerExceptionResolver.invokeExceptionHandler(AnnotatedControllerExceptionResolver.java:232) ~[spring-graphql-1.2.1.jar:1.2.1]
	at org.springframework.graphql.data.method.annotation.support.AnnotatedControllerExceptionResolver.resolveException(AnnotatedControllerExceptionResolver.java:203) ~[spring-graphql-1.2.1.jar:1.2.1]
	at org.springframework.graphql.data.method.annotation.support.AnnotatedControllerConfigurer$SchemaMappingDataFetcher.handleException(AnnotatedControllerConfigurer.java:707) ~[spring-graphql-1.2.1.jar:1.2.1]
	at org.springframework.graphql.data.method.annotation.support.AnnotatedControllerConfigurer$SchemaMappingDataFetcher.lambda$applyExceptionHandling$1(AnnotatedControllerConfigurer.java:696) ~[spring-graphql-1.2.1.jar:1.2.1]
	at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onError(FluxOnErrorResume.java:94) ~[reactor-core-3.5.7.jar:3.5.7]
	at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onError(FluxOnErrorResume.java:106) ~[reactor-core-3.5.7.jar:3.5.7]
	at reactor.core.publisher.Operators.error(Operators.java:198) ~[reactor-core-3.5.7.jar:3.5.7]
	at reactor.core.publisher.MonoError.subscribe(MonoError.java:53) ~[reactor-core-3.5.7.jar:3.5.7]
	at reactor.core.publisher.Mono.subscribe(Mono.java:4495) ~[reactor-core-3.5.7.jar:3.5.7]
	at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onError(FluxOnErrorResume.java:103) ~[reactor-core-3.5.7.jar:3.5.7]
	at reactor.core.publisher.FluxFilter$FilterSubscriber.onError(FluxFilter.java:157) ~[reactor-core-3.5.7.jar:3.5.7]
	at reactor.core.publisher.MonoCreate$DefaultMonoSink.error(MonoCreate.java:201) ~[reactor-core-3.5.7.jar:3.5.7]
	at kotlinx.coroutines.reactor.MonoCoroutine.onCancelled(Mono.kt:111) ~[kotlinx-coroutines-reactor-1.6.4.jar:na]
	at kotlinx.coroutines.AbstractCoroutine.onCompletionInternal(AbstractCoroutine.kt:91) ~[kotlinx-coroutines-core-jvm-1.6.4.jar:na]
	at kotlinx.coroutines.JobSupport.finalizeFinishingState(JobSupport.kt:235) ~[kotlinx-coroutines-core-jvm-1.6.4.jar:na]
	at kotlinx.coroutines.JobSupport.tryMakeCompletingSlowPath(JobSupport.kt:906) ~[kotlinx-coroutines-core-jvm-1.6.4.jar:na]
	at kotlinx.coroutines.JobSupport.tryMakeCompleting(JobSupport.kt:863) ~[kotlinx-coroutines-core-jvm-1.6.4.jar:na]
	at kotlinx.coroutines.JobSupport.makeCompletingOnce$kotlinx_coroutines_core(JobSupport.kt:828) ~[kotlinx-coroutines-core-jvm-1.6.4.jar:na]
	at kotlinx.coroutines.AbstractCoroutine.resumeWith(AbstractCoroutine.kt:100) ~[kotlinx-coroutines-core-jvm-1.6.4.jar:na]
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46) ~[kotlin-stdlib-1.8.22.jar:1.8.22-release-407(1.8.22)]
	at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:367) ~[kotlinx-coroutines-core-jvm-1.6.4.jar:na]
	at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:30) ~[kotlinx-coroutines-core-jvm-1.6.4.jar:na]
	at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable$default(Cancellable.kt:25) ~[kotlinx-coroutines-core-jvm-1.6.4.jar:na]
	at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:110) ~[kotlinx-coroutines-core-jvm-1.6.4.jar:na]
	at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:126) ~[kotlinx-coroutines-core-jvm-1.6.4.jar:na]
	at kotlinx.coroutines.reactor.MonoKt.monoInternal$lambda-2(Mono.kt:90) ~[kotlinx-coroutines-reactor-1.6.4.jar:na]
	at reactor.core.publisher.MonoCreate.subscribe(MonoCreate.java:58) ~[reactor-core-3.5.7.jar:3.5.7]
	at reactor.core.publisher.Mono.subscribe(Mono.java:4495) ~[reactor-core-3.5.7.jar:3.5.7]
	at reactor.core.publisher.Mono.subscribeWith(Mono.java:4561) ~[reactor-core-3.5.7.jar:3.5.7]
	at reactor.core.publisher.Mono.toFuture(Mono.java:5073) ~[reactor-core-3.5.7.jar:3.5.7]
	at org.springframework.graphql.execution.ContextDataFetcherDecorator.get(ContextDataFetcherDecorator.java:107) ~[spring-graphql-1.2.1.jar:1.2.1]
	at graphql.execution.ExecutionStrategy.invokeDataFetcher(ExecutionStrategy.java:309) ~[graphql-java-20.2.jar:na]
	at graphql.execution.ExecutionStrategy.fetchField(ExecutionStrategy.java:286) ~[graphql-java-20.2.jar:na]
	at graphql.execution.ExecutionStrategy.resolveFieldWithInfo(ExecutionStrategy.java:212) ~[graphql-java-20.2.jar:na]
	at graphql.execution.AsyncExecutionStrategy.execute(AsyncExecutionStrategy.java:55) ~[graphql-java-20.2.jar:na]
	at graphql.execution.Execution.executeOperation(Execution.java:161) ~[graphql-java-20.2.jar:na]
	at graphql.execution.Execution.execute(Execution.java:104) ~[graphql-java-20.2.jar:na]
	at graphql.GraphQL.execute(GraphQL.java:557) ~[graphql-java-20.2.jar:na]
	at graphql.GraphQL.lambda$parseValidateAndExecute$11(GraphQL.java:476) ~[graphql-java-20.2.jar:na]
	at java.base/java.util.concurrent.CompletableFuture.uniComposeStage(CompletableFuture.java:1187) ~[na:na]
	at java.base/java.util.concurrent.CompletableFuture.thenCompose(CompletableFuture.java:2309) ~[na:na]
	at graphql.GraphQL.parseValidateAndExecute(GraphQL.java:471) ~[graphql-java-20.2.jar:na]
	at graphql.GraphQL.executeAsync(GraphQL.java:439) ~[graphql-java-20.2.jar:na]
	at org.springframework.graphql.execution.DefaultExecutionGraphQlService.lambda$execute$2(DefaultExecutionGraphQlService.java:83) ~[spring-graphql-1.2.1.jar:1.2.1]
	at reactor.core.publisher.MonoDeferContextual.subscribe(MonoDeferContextual.java:47) ~[reactor-core-3.5.7.jar:3.5.7]
	at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64) ~[reactor-core-3.5.7.jar:3.5.7]
	at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:165) ~[reactor-core-3.5.7.jar:3.5.7]
	at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:79) ~[reactor-core-3.5.7.jar:3.5.7]
	at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:79) ~[reactor-core-3.5.7.jar:3.5.7]
	at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:158) ~[reactor-core-3.5.7.jar:3.5.7]
	at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onNext(FluxContextWrite.java:107) ~[reactor-core-3.5.7.jar:3.5.7]
	at reactor.core.publisher.FluxMapFuseable$MapFuseableConditionalSubscriber.onNext(FluxMapFuseable.java:299) ~[reactor-core-3.5.7.jar:3.5.7]
	at reactor.core.publisher.FluxFilterFuseable$FilterFuseableConditionalSubscriber.onNext(FluxFilterFuseable.java:337) ~[reactor-core-3.5.7.jar:3.5.7]
	at reactor.core.publisher.Operators$BaseFluxToMonoOperator.completePossiblyEmpty(Operators.java:2071) ~[reactor-core-3.5.7.jar:3.5.7]
	at reactor.core.publisher.MonoCollect$CollectSubscriber.onComplete(MonoCollect.java:145) ~[reactor-core-3.5.7.jar:3.5.7]
	at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:144) ~[reactor-core-3.5.7.jar:3.5.7]
	at reactor.core.publisher.FluxPeek$PeekSubscriber.onComplete(FluxPeek.java:260) ~[reactor-core-3.5.7.jar:3.5.7]
	at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:144) ~[reactor-core-3.5.7.jar:3.5.7]
	at reactor.netty.channel.FluxReceive.onInboundComplete(FluxReceive.java:413) ~[reactor-netty-core-1.1.8.jar:1.1.8]
	at reactor.netty.channel.ChannelOperations.onInboundComplete(ChannelOperations.java:431) ~[reactor-netty-core-1.1.8.jar:1.1.8]
	at reactor.netty.http.server.HttpServerOperations.onInboundNext(HttpServerOperations.java:651) ~[reactor-netty-http-1.1.8.jar:1.1.8]
	at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:113) ~[reactor-netty-core-1.1.8.jar:1.1.8]
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
	at reactor.netty.http.server.HttpTrafficHandler.channelRead(HttpTrafficHandler.java:276) ~[reactor-netty-http-1.1.8.jar:1.1.8]
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:346) ~[netty-codec-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:318) ~[netty-codec-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:788) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:724) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:650) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997) ~[netty-common-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.94.Final.jar:4.1.94.Final]
	at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]

2023-07-16T00:19:36.197+02:00 ERROR 15736 --- [ctor-http-nio-2] s.g.e.ExceptionResolversExceptionHandler : Unresolved NotImplementedError for executionId 525a878c-1

kotlin.NotImplementedError: An operation is not implemented: Not yet implemented, bruh!
	at com.example.graph.NodeServiceImpl.node(NodeServiceImpl.kt:15) ~[main/:na]
	at com.example.graph.NodeController.node$suspendImpl(NodeController.kt:32) ~[main/:na]
	at com.example.graph.NodeController.node(NodeController.kt) ~[main/:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
	at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
	at kotlin.reflect.jvm.internal.calls.CallerImpl$Method.callMethod(CallerImpl.kt:97) ~[kotlin-reflect-1.8.22.jar:1.8.22-release-407(1.8.22)]
	at kotlin.reflect.jvm.internal.calls.CallerImpl$Method$Instance.call(CallerImpl.kt:113) ~[kotlin-reflect-1.8.22.jar:1.8.22-release-407(1.8.22)]
	at kotlin.reflect.jvm.internal.KCallableImpl.call(KCallableImpl.kt:107) ~[kotlin-reflect-1.8.22.jar:1.8.22-release-407(1.8.22)]
	at kotlin.reflect.full.KCallables.callSuspend(KCallables.kt:56) ~[kotlin-reflect-1.8.22.jar:1.8.22-release-407(1.8.22)]
	at org.springframework.core.CoroutinesUtils.lambda$invokeSuspendingFunction$2(CoroutinesUtils.java:108) ~[spring-core-6.0.10.jar:6.0.10]
	at kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt$createCoroutineUnintercepted$$inlined$createCoroutineFromSuspendFunction$IntrinsicsKt__IntrinsicsJvmKt$4.invokeSuspend(IntrinsicsJvm.kt:205) ~[kotlin-stdlib-1.8.22.jar:1.8.22-release-407(1.8.22)]
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) ~[kotlin-stdlib-1.8.22.jar:1.8.22-release-407(1.8.22)]
	at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:367) ~[kotlinx-coroutines-core-jvm-1.6.4.jar:na]
	at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:30) ~[kotlinx-coroutines-core-jvm-1.6.4.jar:na]
	at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable$default(Cancellable.kt:25) ~[kotlinx-coroutines-core-jvm-1.6.4.jar:na]
	at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:110) ~[kotlinx-coroutines-core-jvm-1.6.4.jar:na]
	at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:126) ~[kotlinx-coroutines-core-jvm-1.6.4.jar:na]
	at kotlinx.coroutines.reactor.MonoKt.monoInternal$lambda-2(Mono.kt:90) ~[kotlinx-coroutines-reactor-1.6.4.jar:na]
	at reactor.core.publisher.MonoCreate.subscribe(MonoCreate.java:58) ~[reactor-core-3.5.7.jar:3.5.7]
	at reactor.core.publisher.Mono.subscribe(Mono.java:4495) ~[reactor-core-3.5.7.jar:3.5.7]
	at reactor.core.publisher.Mono.subscribeWith(Mono.java:4561) ~[reactor-core-3.5.7.jar:3.5.7]
	at reactor.core.publisher.Mono.toFuture(Mono.java:5073) ~[reactor-core-3.5.7.jar:3.5.7]
	at org.springframework.graphql.execution.ContextDataFetcherDecorator.get(ContextDataFetcherDecorator.java:107) ~[spring-graphql-1.2.1.jar:1.2.1]
	at graphql.execution.ExecutionStrategy.invokeDataFetcher(ExecutionStrategy.java:309) ~[graphql-java-20.2.jar:na]
	at graphql.execution.ExecutionStrategy.fetchField(ExecutionStrategy.java:286) ~[graphql-java-20.2.jar:na]
	at graphql.execution.ExecutionStrategy.resolveFieldWithInfo(ExecutionStrategy.java:212) ~[graphql-java-20.2.jar:na]
	at graphql.execution.AsyncExecutionStrategy.execute(AsyncExecutionStrategy.java:55) ~[graphql-java-20.2.jar:na]
	at graphql.execution.Execution.executeOperation(Execution.java:161) ~[graphql-java-20.2.jar:na]
	at graphql.execution.Execution.execute(Execution.java:104) ~[graphql-java-20.2.jar:na]
	at graphql.GraphQL.execute(GraphQL.java:557) ~[graphql-java-20.2.jar:na]
	at graphql.GraphQL.lambda$parseValidateAndExecute$11(GraphQL.java:476) ~[graphql-java-20.2.jar:na]
	at java.base/java.util.concurrent.CompletableFuture.uniComposeStage(CompletableFuture.java:1187) ~[na:na]
	at java.base/java.util.concurrent.CompletableFuture.thenCompose(CompletableFuture.java:2309) ~[na:na]
	at graphql.GraphQL.parseValidateAndExecute(GraphQL.java:471) ~[graphql-java-20.2.jar:na]
	at graphql.GraphQL.executeAsync(GraphQL.java:439) ~[graphql-java-20.2.jar:na]
	at org.springframework.graphql.execution.DefaultExecutionGraphQlService.lambda$execute$2(DefaultExecutionGraphQlService.java:83) ~[spring-graphql-1.2.1.jar:1.2.1]
	at reactor.core.publisher.MonoDeferContextual.subscribe(MonoDeferContextual.java:47) ~[reactor-core-3.5.7.jar:3.5.7]
	at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64) ~[reactor-core-3.5.7.jar:3.5.7]
	at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:165) ~[reactor-core-3.5.7.jar:3.5.7]
	at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:79) ~[reactor-core-3.5.7.jar:3.5.7]
	at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:79) ~[reactor-core-3.5.7.jar:3.5.7]
	at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:158) ~[reactor-core-3.5.7.jar:3.5.7]
	at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onNext(FluxContextWrite.java:107) ~[reactor-core-3.5.7.jar:3.5.7]
	at reactor.core.publisher.FluxMapFuseable$MapFuseableConditionalSubscriber.onNext(FluxMapFuseable.java:299) ~[reactor-core-3.5.7.jar:3.5.7]
	at reactor.core.publisher.FluxFilterFuseable$FilterFuseableConditionalSubscriber.onNext(FluxFilterFuseable.java:337) ~[reactor-core-3.5.7.jar:3.5.7]
	at reactor.core.publisher.Operators$BaseFluxToMonoOperator.completePossiblyEmpty(Operators.java:2071) ~[reactor-core-3.5.7.jar:3.5.7]
	at reactor.core.publisher.MonoCollect$CollectSubscriber.onComplete(MonoCollect.java:145) ~[reactor-core-3.5.7.jar:3.5.7]
	at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:144) ~[reactor-core-3.5.7.jar:3.5.7]
	at reactor.core.publisher.FluxPeek$PeekSubscriber.onComplete(FluxPeek.java:260) ~[reactor-core-3.5.7.jar:3.5.7]
	at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:144) ~[reactor-core-3.5.7.jar:3.5.7]
	at reactor.netty.channel.FluxReceive.onInboundComplete(FluxReceive.java:413) ~[reactor-netty-core-1.1.8.jar:1.1.8]
	at reactor.netty.channel.ChannelOperations.onInboundComplete(ChannelOperations.java:431) ~[reactor-netty-core-1.1.8.jar:1.1.8]
	at reactor.netty.http.server.HttpServerOperations.onInboundNext(HttpServerOperations.java:651) ~[reactor-netty-http-1.1.8.jar:1.1.8]
	at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:113) ~[reactor-netty-core-1.1.8.jar:1.1.8]
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
	at reactor.netty.http.server.HttpTrafficHandler.channelRead(HttpTrafficHandler.java:276) ~[reactor-netty-http-1.1.8.jar:1.1.8]
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:346) ~[netty-codec-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:318) ~[netty-codec-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:788) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:724) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:650) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997) ~[netty-common-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.94.Final.jar:4.1.94.Final]
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.94.Final.jar:4.1.94.Final]
	at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]

In my controller class I have the following code:

@Controller
class NodeController(
    private val nodeService: NodeService
) {

    val log: Logger = LoggerFactory.getLogger(NodeController::class.java)

    @QueryMapping
    suspend fun node(@Argument id: String): Node? {
        log.info("Using node id: {}", id)
        return nodeService.node(id).awaitSingleOrNull()
    }

    @QueryMapping
    fun nodes(@Argument ids: List<String>): Flow<Node> {
        log.info("Using node ids: {}", ids)
        return nodeService.nodes(ids).asFlow()
    }

    @GraphQlExceptionHandler
    suspend fun handle(ex: Throwable, env: DataFetchingEnvironment): GraphQLError = coroutineScope {
        log.info("Handling error coroutine")
        GraphQLError.newError()
            .errorType(ErrorType.BAD_REQUEST)
            .message(ex.message)
            .build()

    }

    // This works
    // @GraphQlExceptionHandler
    fun handleT(ex: Throwable, env: DataFetchingEnvironment): GraphQLError {
        log.info("Handling error imperative")
        return GraphQLError.newError()
            .errorType(ErrorType.INTERNAL_ERROR)
            .message(ex.message)
            .build()
    }

    // This works as well
    // @GraphQlExceptionHandler
    fun handleMono(ex: Throwable, env: DataFetchingEnvironment): Mono<GraphQLError> {
        log.info("Handling error reactive")
        return Mono.just(
            GraphQLError.newError()
                .errorType(ErrorType.INTERNAL_ERROR)
                .message(ex.message)
                .build()
        )
    }

}

I am trying not to mix reactor code with coroutines in the same class. And below is the service class intentionally left unimplemented to raise an exception:

@Service
class NodeServiceImpl: NodeService {

    override fun node(id: String): Mono<Node> {
        TODO("Not yet implemented, bruh!")
    }

    override fun nodes(ids: List<String>): Flux<Node> {
        TODO("Not yet implemented, bruh!")
    }
}

with minimalistic schema:

type Query {
  node(id: ID!): Node
  author(id: ID!): Author
}

interface Node {
  id: ID!
}

type Author implements Node{
  id: ID!
  firstName: String
  lastName: String
}

schema {
  query: Query
}

Below is a simple query to reproduce the error:

curl 'http://localhost:8080/graphql' -H 'Accept-Encoding: gzip, deflate, br' -H 'Content-Type: application/json' -H 'Accept: application/json' -H 'Connection: keep-alive' --data-binary '{"query":"query authorDetails($authorId: ID! = \"12345\") {\n  node(id: $authorId) {\n    id\n    __typename\n    ... on Author {\n      firstName\n      lastName\n    }\n  }\n}\n","variables":{"authorId":"QXV0aG9yOjEyMzQ1Njc4OQo="}}' --compressed

Reproducer

I created a reproducer: GraphQL Demo

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Jul 15, 2023
@rstoyanchev rstoyanchev self-assigned this Jul 18, 2023
@rstoyanchev rstoyanchev added type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged labels Jul 18, 2023
@rstoyanchev rstoyanchev added this to the 1.2.2 milestone Jul 18, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

3 participants