-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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_SUSPENDED response when calling coroutine in a suspending controller function which returns Flow #11131
Comments
Workaround:
|
Please create a sample app that reproduces the problem. Make sure you have added Micronaut Kotlin dependency. |
combining Flow and suspend seems a bit weird to me |
I have added a sample project with Flow and it's passing. |
@dstepanov sure, example application here based on your's: https://github.com/vekonypeter/miconaut-core-issue-11131/tree/main I modified your example, because that way it is surely works. The problem is when you have a suspending function, which returns a Flow, but also contains some other suspending coroutine interaction before. See example here: https://github.com/vekonypeter/miconaut-core-issue-11131/blob/main/src/main/kotlin/com/example/HelloController.kt#L14-L18 Test result here is:
Dependencies seem to be fine, micronaut-kotlin-runtime is added. Is this a very exotic use-case? For us, it seems pretty common, because all of our database interactions and calls towards other APIs are done using suspending functions. |
I see, but as Jonas wrote it doesn’t make sense to have suspended Flow. |
@vekonypeter the reason it's weird is that you're combining two reactive programming styles (coroutines and flows). In our framework view it becomes a Index: src/main/kotlin/com/example/HelloController.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/main/kotlin/com/example/HelloController.kt b/src/main/kotlin/com/example/HelloController.kt
--- a/src/main/kotlin/com/example/HelloController.kt (revision abe1e617c596a15913e542f4832d531535516e00)
+++ b/src/main/kotlin/com/example/HelloController.kt (date 1725020074789)
@@ -6,15 +6,16 @@
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow
+import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flow
@Controller("/hello")
class HelloController {
@Get(value = "/world1")
- suspend fun world1(): Flow<String> {
+ fun world1(): Flow<String> = flow {
delay(1000)
- return listOf("Hello World").asFlow()
+ emitAll(listOf("Hello World").asFlow())
}
@Get(value = "/world2", produces = [MediaType.TEXT_PLAIN]) Index: src/main/kotlin/com/example/HelloController.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/main/kotlin/com/example/HelloController.kt b/src/main/kotlin/com/example/HelloController.kt
--- a/src/main/kotlin/com/example/HelloController.kt (revision abe1e617c596a15913e542f4832d531535516e00)
+++ b/src/main/kotlin/com/example/HelloController.kt (date 1725020169453)
@@ -7,14 +7,15 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.single
@Controller("/hello")
class HelloController {
@Get(value = "/world1")
- suspend fun world1(): Flow<String> {
+ suspend fun world1(): String {
delay(1000)
- return listOf("Hello World").asFlow()
+ return listOf("Hello World").asFlow().single()
}
@Get(value = "/world2", produces = [MediaType.TEXT_PLAIN]) |
I don't fully understand what you mean by "combining two reactive programming styles (coroutines and flows)". Flows are an integral part of Kotlin coroutines. Maybe the example is just too simple, but imagine the following use-case:
in code: suspend fun test(): Flow<Any> {
val res = httpClientMethodCall() // this is a suspending function therefore test() must be also suspending
return readSomeStuffFromDb(res)
} I don't think that this is something super weird, pretty usual when you have everything implemented with coroutines and suspending functions. But this will return the "COROUTINE_SUSPENDED" response for sure. I can make it work somehow like this and probably in a lot of other ways too, but it feels odd: fun test(): Flow<Any> = flow {
emit(httpClientMethodCall())
}.map { res ->
readSomeStuffFromDb(res)
} |
Both a suspend method and a Flow represent the same thing: A result that will be produced asynchronously in the future (in reactive streams terms, both are a flow). Flow has some added features like multiple items and multiple consumers, but it is conceptually the same thing. When you have a suspend method that returns a Flow, the framework first has to wait for the suspend method to complete, and then also for the flow to complete. This form of double flow is not supported. FWIW, this is not exactly idiomatic in general kotlin code either, because it can be annoying to work with even without micronaut involved. This SO answer describes it well: https://stackoverflow.com/a/76031024 |
Expected Behavior
I have a class annotated with
@Controller
and my method returnFlow<...>
as response. Inside the method I have to call another suspending function, because e.g. I want to fetch data from the database or call another API which all works via coroutines for me. Due to this my controller method is also a suspending.When a client calls this method it should get the data sent via the
Flow
.Actual Behaviour
status code is 200 , but the response is the following:
Steps To Reproduce
Environment Information
Example Application
No response
Version
4.4.2
The text was updated successfully, but these errors were encountered: