Micronaut-core: Using Suspend in Client Functions does not unwrap objects correctly

Created on 13 May 2020  路  5Comments  路  Source: micronaut-projects/micronaut-core

Task List

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

Steps to Reproduce

Use "suspend" modifier in any Client interface function.
Run the example application and go to "http://localhost:8080/test"

Expected Behaviour

The app should retrieve the data from the client call and unwrap it into the appropriate class type given in the function's signature.

Actual Behaviour

Instead of unwrapping to a type, it is returned as a HashMap, which causes the application to fail as the types are incorrect.

The issue appears to be that when using "suspend" in the client interfaces against any function, the generated code is not unwrapping the resultant data into the class types specified.

Break-pointing the example code on line 28 of Controller.kt, you will see the returned data is:
image
i.e. a list of HashMaps instead of a list of the correct type.
The code then fails to transform this as the types are wrong, and throws an exception.

Environment Information

  • Operating System: Linux
  • Micronaut Version: 1.3.5
  • JDK Version: 12+

Example Application

https://github.com/markjfisher/mn-client-suspend

Stacktrace

java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class mn.client.suspend.SomeType (java.util.LinkedHashMap is in module java.base of loader 'bootstrap'; mn.client.suspend.SomeType is in unnamed module of loader 'app')
    at mn.client.suspend.Controller.doTest(Controller.kt:34)
    at mn.client.suspend.$ControllerDefinition$$exec2.invokeInternal(Unknown Source)
    at io.micronaut.context.AbstractExecutableMethod.invoke(AbstractExecutableMethod.java:146)
    at io.micronaut.context.DefaultBeanContext$BeanExecutionHandle.invoke(DefaultBeanContext.java:3016)
    at io.micronaut.web.router.AbstractRouteMatch.execute(AbstractRouteMatch.java:286)
    at io.micronaut.web.router.RouteMatch.execute(RouteMatch.java:122)
    at io.micronaut.http.server.netty.RoutingInBoundHandler.executeKotlinSuspendingFunction(RoutingInBoundHandler.java:1571)
    at io.micronaut.http.server.netty.RoutingInBoundHandler.lambda$buildResultEmitter$14(RoutingInBoundHandler.java:1427)
    at io.reactivex.internal.operators.flowable.FlowableDefer.subscribeActual(FlowableDefer.java:35)
    at io.reactivex.Flowable.subscribe(Flowable.java:14918)
    at io.reactivex.internal.operators.flowable.FlowableMap.subscribeActual(FlowableMap.java:37)
    at io.reactivex.Flowable.subscribe(Flowable.java:14918)
    at io.reactivex.internal.operators.flowable.FlowableSwitchIfEmpty.subscribeActual(FlowableSwitchIfEmpty.java:32)
    at io.reactivex.Flowable.subscribe(Flowable.java:14918)
    at io.reactivex.Flowable.subscribe(Flowable.java:14868)
    at io.micronaut.http.context.ServerRequestTracingPublisher.lambda$subscribe$0(ServerRequestTracingPublisher.java:52)
    at io.micronaut.http.context.ServerRequestContext.with(ServerRequestContext.java:68)
    at io.micronaut.http.context.ServerRequestTracingPublisher.subscribe(ServerRequestTracingPublisher.java:52)
    at io.reactivex.internal.operators.flowable.FlowableFromPublisher.subscribeActual(FlowableFromPublisher.java:29)
    at io.reactivex.Flowable.subscribe(Flowable.java:14918)
    at io.reactivex.Flowable.subscribe(Flowable.java:14865)
    at io.reactivex.internal.operators.flowable.FlowableSubscribeOn$SubscribeOnSubscriber.run(FlowableSubscribeOn.java:82)
    at io.reactivex.internal.schedulers.ExecutorScheduler$ExecutorWorker$BooleanRunnable.run(ExecutorScheduler.java:288)
    at io.reactivex.internal.schedulers.ExecutorScheduler$ExecutorWorker.run(ExecutorScheduler.java:253)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.lang.Thread.run(Thread.java:835)

Other references

I've just noticed this was also posted at #3058 but had already written this cutdown example, so thought I'd raise it anyway.

kotlin

Most helpful comment

@bjuretic @jaecktec @markjfisher This should work in Micronaut 2.1

All 5 comments

Simply removing the "suspend" keywords in the Client interfaces works, but (I believe) is then blocking. This has come to light in our performance testing, I've rewritten a lot of our code from RX Single returns to suspend functions, but discovered this issue along the way. I'm about to attempt to manually transform the hashmap to the right type to move this along, or find some bridge back to the RX world until this is fixed.

I believe this is linked to problems with kapt not feeding us the correct arguments types for generics

@graemerocher So, can this be fixed at all? I see that Spring has CoroutineRestOperations and generally https://github.com/konrad-kaminski/spring-kotlin-coroutine for all relevant use cases have solved the issue.

We went with compleatable futures and used the kotlinx jdk8 library to 'await' the response. However this is ofc just a workaround, having suspend directly in the interface would be great

@bjuretic @jaecktec @markjfisher This should work in Micronaut 2.1

Was this page helpful?
0 / 5 - 0 ratings