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

Declarative client with suspended function fails on 404 #5301

Closed
4 tasks done
jaecktec opened this issue Mar 15, 2021 · 10 comments · Fixed by #5575
Closed
4 tasks done

Declarative client with suspended function fails on 404 #5301

jaecktec opened this issue Mar 15, 2021 · 10 comments · Fixed by #5575
Labels
info: good first issue Good for newcomers lang: kotlin Issues or features specific to Kotlin type: bug Something isn't working

Comments

@jaecktec
Copy link

jaecktec commented Mar 15, 2021

Task List

  • Stacktrace (if present) provided
  • Steps to reproduce provided
  • Example that reproduces the problem uploaded to Github
  • Full description of the issue provided (see below)

Steps to Reproduce

  1. Create declarative client with suspended function and nullable response tye
  2. execute request to a resource that returns 404

Expected Behaviour

the client returns null

Actual Behaviour

throws with java.lang.IllegalStateException

java.lang.IllegalStateException: Cannot complete Kotlin coroutine with null: class com.example.SampleDeclarativeClient$SomeResponseObject
	at io.micronaut.aop.internal.intercepted.KotlinInterceptedMethod.lambda$handleResult$1(KotlinInterceptedMethod.java:155)
	at java.base/java.util.concurrent.CompletableFuture.uniWhenComplete(CompletableFuture.java:859)
	at java.base/java.util.concurrent.CompletableFuture$UniWhenComplete.tryFire(CompletableFuture.java:837)
	at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:506)
	at java.base/java.util.concurrent.CompletableFuture.complete(CompletableFuture.java:2073)
	at io.micronaut.http.client.interceptor.HttpClientIntroductionAdvice$1.doOnError(HttpClientIntroductionAdvice.java:379)
	at io.micronaut.core.async.subscriber.CompletionAwareSubscriber.onError(CompletionAwareSubscriber.java:63)
	at io.reactivex.internal.util.HalfSerializer.onError(HalfSerializer.java:70)
	at io.reactivex.internal.subscribers.StrictSubscriber.onError(StrictSubscriber.java:103)
	at io.micronaut.core.async.publisher.Publishers$1.doOnError(Publishers.java:218)
	at io.micronaut.core.async.subscriber.CompletionAwareSubscriber.onError(CompletionAwareSubscriber.java:63)
	at io.reactivex.internal.util.HalfSerializer.onError(HalfSerializer.java:70)
	at io.reactivex.internal.subscribers.StrictSubscriber.onError(StrictSubscriber.java:103)
	at io.reactivex.internal.operators.flowable.FlowableSwitchMap$SwitchMapSubscriber.drain(FlowableSwitchMap.java:221)
	at io.reactivex.internal.operators.flowable.FlowableSwitchMap$SwitchMapInnerSubscriber.onError(FlowableSwitchMap.java:403)
	at io.reactivex.internal.operators.flowable.FlowableOnErrorNext$OnErrorNextSubscriber.onError(FlowableOnErrorNext.java:90)
	at io.reactivex.internal.subscriptions.EmptySubscription.error(EmptySubscription.java:55)
	at io.reactivex.internal.operators.flowable.FlowableError.subscribeActual(FlowableError.java:40)
	at io.reactivex.Flowable.subscribe(Flowable.java:14918)
	at io.reactivex.Flowable.subscribe(Flowable.java:14865)
	at io.reactivex.internal.operators.flowable.FlowableOnErrorNext$OnErrorNextSubscriber.onError(FlowableOnErrorNext.java:115)
	at io.reactivex.internal.operators.flowable.FlowableTimeoutTimed$TimeoutSubscriber.onError(FlowableTimeoutTimed.java:115)
	at io.reactivex.internal.operators.flowable.FlowableCreate$BaseEmitter.error(FlowableCreate.java:292)
	at io.reactivex.internal.operators.flowable.FlowableCreate$BaseEmitter.tryOnError(FlowableCreate.java:281)
	at io.micronaut.http.client.netty.DefaultHttpClient$12.channelReadInstrumented(DefaultHttpClient.java:2166)
	at io.micronaut.http.client.netty.DefaultHttpClient$12.channelReadInstrumented(DefaultHttpClient.java:2076)
	at io.micronaut.http.client.netty.DefaultHttpClient$SimpleChannelInboundHandlerInstrumented.channelRead0(DefaultHttpClient.java:2776)
	at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:99)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
	at io.micronaut.http.netty.stream.HttpStreamsHandler.channelRead(HttpStreamsHandler.java:193)
	at io.micronaut.http.netty.stream.HttpStreamsClientHandler.channelRead(HttpStreamsClientHandler.java:183)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
	at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:103)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
	at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:103)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
	at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436)
	at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:324)
	at io.netty.handler.codec.ByteToMessageDecoder.channelInputClosed(ByteToMessageDecoder.java:383)
	at io.netty.handler.codec.ByteToMessageDecoder.channelInactive(ByteToMessageDecoder.java:354)
	at io.netty.handler.codec.http.HttpClientCodec$Decoder.channelInactive(HttpClientCodec.java:311)
	at io.netty.channel.CombinedChannelDuplexHandler.channelInactive(CombinedChannelDuplexHandler.java:221)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelInactive(AbstractChannelHandlerContext.java:262)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelInactive(AbstractChannelHandlerContext.java:248)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelInactive(AbstractChannelHandlerContext.java:241)
	at io.netty.channel.ChannelInboundHandlerAdapter.channelInactive(ChannelInboundHandlerAdapter.java:81)
	at io.netty.handler.timeout.IdleStateHandler.channelInactive(IdleStateHandler.java:277)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelInactive(AbstractChannelHandlerContext.java:262)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelInactive(AbstractChannelHandlerContext.java:248)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelInactive(AbstractChannelHandlerContext.java:241)
	at io.netty.channel.DefaultChannelPipeline$HeadContext.channelInactive(DefaultChannelPipeline.java:1405)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelInactive(AbstractChannelHandlerContext.java:262)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelInactive(AbstractChannelHandlerContext.java:248)
	at io.netty.channel.DefaultChannelPipeline.fireChannelInactive(DefaultChannelPipeline.java:901)
	at io.netty.channel.AbstractChannel$AbstractUnsafe$8.run(AbstractChannel.java:819)
	at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:164)
	at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:472)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:497)
	at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
	at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.base/java.lang.Thread.run(Thread.java:834)

Environment Information

  • Operating System: macosx 11.2.2
  • Micronaut Version: 2.4.0
  • JDK Version: openjdk 11.0.9.1 2020-11-04

Example Application

https://github.com/jaecktec/micronaut-issues/tree/declarative-suspend-client-null-issue/

@jaecktec
Copy link
Author

Is this a micronaut-core issue?

@graemerocher graemerocher transferred this issue from micronaut-projects/micronaut-kotlin Apr 23, 2021
@graemerocher graemerocher added type: bug Something isn't working lang: kotlin Issues or features specific to Kotlin labels Apr 23, 2021
@bjor-joh
Copy link

Any updates or plans to fix this?

@graemerocher
Copy link
Contributor

Not yet, but PRs are welcome of course

@jaecktec
Copy link
Author

I'd love to contribute however I have no idea where to start. Could you point me in a good starting direction?

@graemerocher
Copy link
Contributor

from the stack trace it looks like HttpClientIntroductionAdvice emits null for completion stage with a 404:

This appears to be invalid in Kotlin coroutines so the return type here:

Would need to be checked for returnType.isSuspend() and instead maybe complete by calling exceptionally on the completion stage with the exception if it is a suspend function

@graemerocher graemerocher added the info: good first issue Good for newcomers label May 19, 2021
@wlezzar
Copy link
Contributor

wlezzar commented Jun 11, 2021

This appears to be invalid in Kotlin coroutines so the return type here:

@graemerocher Completing kotlin coroutines with null should be totally valid.

I think what is making this failing is this line here which explicitely throws the exception when the result is null.

Do you have some context on why this behavior has been setup?

@graemerocher
Copy link
Contributor

good question, @dstepanov any idea?

@dstepanov
Copy link
Contributor

I guess I didn't realize that the coroutine can return a null.
Anyone wants to create a PR with a test and removing that line?

@wlezzar
Copy link
Contributor

wlezzar commented Jun 11, 2021

Yes I can do that 👍🏼

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
info: good first issue Good for newcomers lang: kotlin Issues or features specific to Kotlin type: bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants