I'm trying to make example chat application and I'm getting strange error when using fire-and-forget from my ReactJs rsocket-websocket-client.
Error:
java.lang.IllegalArgumentException: Missing 'rsocketResponse'
at org.springframework.util.Assert.notNull(Assert.java:201) ~[spring-core-5.3.6.jar:5.3.6]
at org.springframework.messaging.rsocket.annotation.support.RSocketPayloadReturnValueHandler.handleEncodedContent(RSocketPayloadReturnValueHandler.java:64) ~[spring-messaging-5.3.6.jar:5.3.6]
at org.springframework.messaging.handler.invocation.reactive.AbstractEncoderMethodReturnValueHandler.lambda$handleReturnValue$0(AbstractEncoderMethodReturnValueHandler.java:121) ~[spring-messaging-5.3.6.jar:5.3.6]
at org.springframework.messaging.handler.invocation.reactive.ChannelSendOperator$WriteBarrier.onComplete(ChannelSendOperator.java:251) ~[spring-messaging-5.3.6.jar:5.3.6]
at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:142) ~[reactor-core-3.4.5.jar:3.4.5]
at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onComplete(Operators.java:2057) ~[reactor-core-3.4.5.jar:3.4.5]
at reactor.core.publisher.MonoCreate$DefaultMonoSink.success(MonoCreate.java:133) ~[reactor-core-3.4.5.jar:3.4.5]
at kotlinx.coroutines.reactor.MonoCoroutine.onCompleted(Mono.kt:66) ~[kotlinx-coroutines-reactor-1.4.3.jar:na]
at kotlinx.coroutines.AbstractCoroutine.onCompletionInternal(AbstractCoroutine.kt:104) ~[kotlinx-coroutines-core-jvm-1.4.3.jar:na]
at kotlinx.coroutines.JobSupport.tryFinalizeSimpleState(JobSupport.kt:294) ~[kotlinx-coroutines-core-jvm-1.4.3.jar:na]
at kotlinx.coroutines.JobSupport.tryMakeCompleting(JobSupport.kt:856) ~[kotlinx-coroutines-core-jvm-1.4.3.jar:na]
at kotlinx.coroutines.JobSupport.makeCompletingOnce$kotlinx_coroutines_core(JobSupport.kt:828) ~[kotlinx-coroutines-core-jvm-1.4.3.jar:na]
at kotlinx.coroutines.AbstractCoroutine.resumeWith(AbstractCoroutine.kt:111) ~[kotlinx-coroutines-core-jvm-1.4.3.jar:na]
at kotlinx.coroutines.debug.internal.DebugProbesImpl$CoroutineOwner.resumeWith(DebugProbesImpl.kt:461) ~[kotlinx-coroutines-core-jvm-1.4.3.jar:na]
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46) ~[kotlin-stdlib-1.4.32.jar:1.4.32-release-371 (1.4.32)]
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106) ~[kotlinx-coroutines-core-jvm-1.4.3.jar:na]
at kotlinx.coroutines.EventLoop.processUnconfinedEvent(EventLoop.common.kt:69) ~[kotlinx-coroutines-core-jvm-1.4.3.jar:na]
at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:357) ~[kotlinx-coroutines-core-jvm-1.4.3.jar:na]
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:30) ~[kotlinx-coroutines-core-jvm-1.4.3.jar:na]
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable$default(Cancellable.kt:27) ~[kotlinx-coroutines-core-jvm-1.4.3.jar:na]
at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:110) ~[kotlinx-coroutines-core-jvm-1.4.3.jar:na]
at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:158) ~[kotlinx-coroutines-core-jvm-1.4.3.jar:na]
at kotlinx.coroutines.reactor.MonoKt$monoInternal$1.accept(Mono.kt:55) ~[kotlinx-coroutines-reactor-1.4.3.jar:na]
at kotlinx.coroutines.reactor.MonoKt$monoInternal$1.accept(Mono.kt) ~[kotlinx-coroutines-reactor-1.4.3.jar:na]
at reactor.core.publisher.MonoCreate.subscribe(MonoCreate.java:57) ~[reactor-core-3.4.5.jar:3.4.5]
at reactor.core.publisher.InternalFluxOperator.subscribe(InternalFluxOperator.java:62) ~[reactor-core-3.4.5.jar:3.4.5]
at org.springframework.messaging.handler.invocation.reactive.ChannelSendOperator.subscribe(ChannelSendOperator.java:85) ~[spring-messaging-5.3.6.jar:5.3.6]
at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:157) ~[reactor-core-3.4.5.jar:3.4.5]
at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:73) ~[reactor-core-3.4.5.jar:3.4.5]
at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1815) ~[reactor-core-3.4.5.jar:3.4.5]
at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:151) ~[reactor-core-3.4.5.jar:3.4.5]
at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1815) ~[reactor-core-3.4.5.jar:3.4.5]
at reactor.core.publisher.MonoZip$ZipCoordinator.signal(MonoZip.java:251) ~[reactor-core-3.4.5.jar:3.4.5]
at reactor.core.publisher.MonoZip$ZipInner.onNext(MonoZip.java:336) ~[reactor-core-3.4.5.jar:3.4.5]
at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.onNext(MonoPeekTerminal.java:180) ~[reactor-core-3.4.5.jar:3.4.5]
at reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:2397) ~[reactor-core-3.4.5.jar:3.4.5]
at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.request(MonoPeekTerminal.java:139) ~[reactor-core-3.4.5.jar:3.4.5]
at reactor.core.publisher.MonoZip$ZipInner.onSubscribe(MonoZip.java:325) ~[reactor-core-3.4.5.jar:3.4.5]
at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.onSubscribe(MonoPeekTerminal.java:152) ~[reactor-core-3.4.5.jar:3.4.5]
at reactor.core.publisher.MonoJust.subscribe(MonoJust.java:54) ~[reactor-core-3.4.5.jar:3.4.5]
at reactor.core.publisher.Mono.subscribe(Mono.java:4150) ~[reactor-core-3.4.5.jar:3.4.5]
at reactor.core.publisher.MonoZip.subscribe(MonoZip.java:128) ~[reactor-core-3.4.5.jar:3.4.5]
at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64) ~[reactor-core-3.4.5.jar:3.4.5]
at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52) ~[reactor-core-3.4.5.jar:3.4.5]
at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64) ~[reactor-core-3.4.5.jar:3.4.5]
at io.rsocket.core.RSocketResponder.handleFireAndForget(RSocketResponder.java:313) ~[rsocket-core-1.1.0.jar:na]
at io.rsocket.core.RSocketResponder.handleFrame(RSocketResponder.java:197) ~[rsocket-core-1.1.0.jar:na]
at reactor.core.publisher.LambdaSubscriber.onNext(LambdaSubscriber.java:160) ~[reactor-core-3.4.5.jar:3.4.5]
at io.rsocket.core.ClientServerInputMultiplexer$InternalDuplexConnection.onNext(ClientServerInputMultiplexer.java:248) ~[rsocket-core-1.1.0.jar:na]
at io.rsocket.core.ClientServerInputMultiplexer.onNext(ClientServerInputMultiplexer.java:129) ~[rsocket-core-1.1.0.jar:na]
at io.rsocket.core.ClientServerInputMultiplexer.onNext(ClientServerInputMultiplexer.java:48) ~[rsocket-core-1.1.0.jar:na]
at io.rsocket.core.SetupHandlingDuplexConnection.onNext(SetupHandlingDuplexConnection.java:118) ~[rsocket-core-1.1.0.jar:na]
at io.rsocket.core.SetupHandlingDuplexConnection.onNext(SetupHandlingDuplexConnection.java:19) ~[rsocket-core-1.1.0.jar:na]
at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:120) ~[reactor-core-3.4.5.jar:3.4.5]
at reactor.netty.channel.FluxReceive.drainReceiver(FluxReceive.java:280) ~[reactor-netty-core-1.0.6.jar:1.0.6]
at reactor.netty.channel.FluxReceive.onInboundNext(FluxReceive.java:389) ~[reactor-netty-core-1.0.6.jar:1.0.6]
at reactor.netty.channel.ChannelOperations.onInboundNext(ChannelOperations.java:401) ~[reactor-netty-core-1.0.6.jar:1.0.6]
at reactor.netty.http.server.HttpServerOperations.onInboundNext(HttpServerOperations.java:560) ~[reactor-netty-http-1.0.6.jar:1.0.6]
at reactor.netty.http.server.WebsocketServerOperations.onInboundNext(WebsocketServerOperations.java:168) ~[reactor-netty-http-1.0.6.jar:1.0.6]
at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:94) ~[reactor-netty-core-1.0.6.jar:1.0.6]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) ~[netty-transport-4.1.63.Final.jar:4.1.63.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) ~[netty-transport-4.1.63.Final.jar:4.1.63.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357) ~[netty-transport-4.1.63.Final.jar:4.1.63.Final]
at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:324) ~[netty-codec-4.1.63.Final.jar:4.1.63.Final]
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:296) ~[netty-codec-4.1.63.Final.jar:4.1.63.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) ~[netty-transport-4.1.63.Final.jar:4.1.63.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) ~[netty-transport-4.1.63.Final.jar:4.1.63.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357) ~[netty-transport-4.1.63.Final.jar:4.1.63.Final]
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410) ~[netty-transport-4.1.63.Final.jar:4.1.63.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) ~[netty-transport-4.1.63.Final.jar:4.1.63.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) ~[netty-transport-4.1.63.Final.jar:4.1.63.Final]
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919) ~[netty-transport-4.1.63.Final.jar:4.1.63.Final]
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166) ~[netty-transport-4.1.63.Final.jar:4.1.63.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:719) ~[netty-transport-4.1.63.Final.jar:4.1.63.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:655) ~[netty-transport-4.1.63.Final.jar:4.1.63.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:581) ~[netty-transport-4.1.63.Final.jar:4.1.63.Final]
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493) ~[netty-transport-4.1.63.Final.jar:4.1.63.Final]
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989) ~[netty-common-4.1.63.Final.jar:4.1.63.Final]
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.63.Final.jar:4.1.63.Final]
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.63.Final.jar:4.1.63.Final]
at java.base/java.lang.Thread.run(Thread.java:832) ~[na:na]
Example app SpringBoot + ReactJs Client: rsocketsdemo.zip
- Start SpringBoot
- Client - npm install (for node modules)
- Client - npm start
- Client - write message and hit send button
Comment From: rstoyanchev
@sdeleuze this looks closely related to #23866 and the explanation under https://github.com/spring-projects/spring-framework/issues/23866#issuecomment-548298068 still applies but the actual case might be slightly different. Not sure if the issue is with the return type of the method signature or with the way we interpret it, but either way for fire-and-forget there should be no response from the controller method but it appears as if there is.
Comment From: GeorgePap-719
Im also having this problem. I can provide a simple reproducible project if would help you.
Comment From: GeorgePap-719
https://github.com/GeorgePap-719/simple-springboot-kotlin I have created a project where it reproduces the error and i wrote some tests to validate the error.
Comment From: poutsma
The cause seems to be that InvocableHandlerMethod::isAsyncVoidReturnType
does not correctly recognize Kotlin coroutines that return Unit
(Kotlin's Void
type).
@sdeleuze & @rstoyanchev I have a proposed fix here. Could you please review?
FWIW, I tried to apply the same change to org.springframework.web.reactive.result.method.InvocableHandlerMethod:: isAsyncVoidReturnType
, but that results in failing tests.
Comment From: sdeleuze
The logic seems ok, but maybe worth to check if you could just reuse the existing logic implemented in MethodParameter
and accessible via MethodParameter#getParameterType
. Also worth to add Kotlin tests to test this specific arrangement.
Could you please share more about what happen on org.springframework.web.reactive.result.method.InvocableHandlerMethod:: isAsyncVoidReturnType
?
Comment From: poutsma
The logic seems ok, but maybe worth to check if you could just reuse the existing logic implemented in
MethodParameter
and accessible viaMethodParameter#getParameterType
.
Done, see https://github.com/spring-projects/spring-framework/commit/c55606ed08609653c537c6da6c8900fc8ea07513.
Also worth to add Kotlin tests to test this specific arrangement.
There already is one for fire-and-forget. The IllegalArgumentException
was non-fatal, and just logged.
Could you please share more about what happen on
org.springframework.web.reactive.result.method.InvocableHandlerMethod:: isAsyncVoidReturnType
?
I wanted to make the same change in WebFlux, because it seemed to make sense. Once I added this change in that InvocableHandlerMethod:: isAsyncVoidReturnType
, tests failed. I am assuming this is because WebFlux already supports Unit
-returning methods in other ways, but I am not sure and did not investigate.