How can I make sure all Kotlin coroutines created by a ktor websocket client are cleared up? - kotlin

I'm trying to wrap my head around Kotlin coroutines and Ktors websocket support. My understanding is that runBlocking will create a scope and that it will block as long as there are coroutines living inside that scope (or child scopes), but I when the call to runBlocking in the test below returns there are still two coroutines alive..
Why am I leaking coroutines here?
package dummy
import io.ktor.client.HttpClient
import io.ktor.client.features.websocket.WebSockets
import io.ktor.client.features.websocket.wss
import io.ktor.http.HttpMethod
import io.ktor.http.cio.websocket.Frame
import io.ktor.http.cio.websocket.readBytes
import io.ktor.http.cio.websocket.readText
import io.ktor.util.KtorExperimentalAPI
import kotlinx.coroutines.*
import kotlinx.coroutines.debug.DebugProbes
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
#ExperimentalCoroutinesApi
#KtorExperimentalAPI
class WebsocketTest {
#Test
fun tidy() {
DebugProbes.install()
runBlocking {
val socketJob = Job()
launch(CoroutineName("Websocket") + socketJob) {
println("Connecting to websocket")
connectWebsocket(socketJob)
println("Websocket dead?")
}
launch(CoroutineName("Ninja socket killer")) {
delay(3500)
println("Killing websocket client")
socketJob.cancel(message = "Time to die..")
}
}
println("\n\n-------")
DebugProbes.dumpCoroutines(System.err)
Assertions.assertEquals(0, DebugProbes.dumpCoroutinesInfo().size, "It would be nice if all coroutines had been cleared up by now..")
}
}
#KtorExperimentalAPI
private suspend fun connectWebsocket(socketJob: CompletableJob) {
val client = HttpClient {
install(WebSockets)
}
socketJob.invokeOnCompletion {
println("Shutting down ktor http client")
client.close()
}
client.wss(
method = HttpMethod.Get,
host = "echo.websocket.org",
port = 443,
path = "/"
) {
send(Frame.Text("Hello World"))
for (frame in incoming) {
when (frame) {
is Frame.Text -> println(frame.readText())
is Frame.Binary -> println(frame.readBytes())
}
delay(1000)
send(Frame.Text("Hello World"))
}
}
}
build.gradle.kts
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import org.gradle.api.tasks.testing.logging.TestLogEvent
plugins {
kotlin("jvm") version "1.3.41" apply true
}
repositories {
mavenCentral()
}
val ktorVersion = "1.2.3"
val junitVersion = "5.5.1"
dependencies {
implementation(kotlin("stdlib-jdk8"))
implementation("io.ktor:ktor-client-websockets:$ktorVersion")
implementation("io.ktor:ktor-client-okhttp:$ktorVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-debug:1.3.0-RC2")
testImplementation("org.junit.jupiter:junit-jupiter-api:$junitVersion")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$junitVersion")
}
tasks.withType<Test> {
useJUnitPlatform()
testLogging {
showExceptions = true
showStackTraces = true
exceptionFormat = TestExceptionFormat.FULL
events = setOf(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED)
}
}

Seems like I have figured it out (obviously just after ripping my hair out long enough to make this post in the first place). When I wrote the post I leaked two coroutines and one of them "solved itself" (I'm not very happy about that, but what ever I do I can't reproduce it).
The second coroutine leaked because Nonce.kt from Ktor explicitly launches a coroutine in GlobalScope.
https://github.com/ktorio/ktor/blob/master/ktor-utils/jvm/src/io/ktor/util/Nonce.kt#L30
private val nonceGeneratorJob =
GlobalScope.launch(
context = Dispatchers.IO + NonCancellable + NonceGeneratorCoroutineName,
start = CoroutineStart.LAZY
) { ....

Related

Using async func in Compose Web

I should use Ktor client in Compose Web. But, It can't be called in Compose Web due to async/non-async problem.
Environment is template project made by IntelliJ IDEA.
First, I use this:
val client=HttpClient(Js){
install(ContentNegotiation){
json()
}
}
suspend fun shorterCall(url:String):String{
val response = client.get(SERVER_NAME) {
contentType(ContentType.Application.Json)
parameter("url", url)
}
return response.bodyAsText()
}
suspend fun main() {
var i by mutableStateOf("")
renderComposable(rootElementId = "root") {
Div({ style {
height(100.vh)
margin(0.px)
width(100.vw)
} }) {
Input(type = InputType.Url) {
onInput {
val input=it.value.trim()
if(input.startsWith("http"))
i=shorterCall(input)
else
i="NaN"
}
}
Text(i)
}
}
}
Then, I got that error:
Suspend function can be called only within a coroutine body.
So, I tried another one:
import kotlinx.coroutines.*
fun shorterCall(url:String):String{
var ret:String
suspend fun t():String {
val response = client.get(SERVER_NAME) {
contentType(ContentType.Application.Json)
parameter("url", url)
}
return response.bodyAsText()
}
runBlocking{
ret=t()
}
return ret
}
//main func is same as upper one.
Then, I got that error:
Unresolved reference: runBlocking
+editing body 1: When I use GlobalScope.launch or js("JSCode"), It raise that error:
e: Could not find "kotlin" in [/home/user/.local/share/kotlin/daemon]
e: java.lang.IllegalStateException: FATAL ERROR: Could not find "kotlin" in [/home/user/.local/share/kotlin/daemon]
(a lot of internal errors bellow)
You can use the GlobalScope.launch() method to launch a job for a request in a browser environment:
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import io.ktor.client.*
import io.ktor.client.engine.js.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.jetbrains.compose.web.attributes.InputType
import org.jetbrains.compose.web.css.*
import org.jetbrains.compose.web.dom.*
import org.jetbrains.compose.web.renderComposable
fun main() {
val client = HttpClient(Js) {}
var i by mutableStateOf("")
renderComposable(rootElementId = "root") {
Div({
style {
height(100.vh)
margin(0.px)
width(100.vw)
}
}) {
Input(type = InputType.Url) {
onInput {
val input = it.value.trim()
if (input.startsWith("http")) {
GlobalScope.launch {
i = client.shorterCall(input)
}
} else {
i = "NaN"
}
}
}
Text(i)
}
}
}
suspend fun HttpClient.shorterCall(url: String): String {
val response = get(url) {
contentType(ContentType.Application.Json)
}
return response.bodyAsText()
}

How to build nested routes in Ktor?

I defined my routes in the separate file:
PostRoutes.kt:
fun Route.getPostsRoute() {
get("/posts") {
call.respondText("Posts")
}
}
// Some other routes
fun Application.postRoutes() {
routing {
getPostsRoute()
// Some other routes
}
}
And I setup these routes in Application.kt as it shown below:
fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args)
fun Application.module(testing: Boolean = false) {
routing { // I want to provide the root endpoint (/api/v1) here
postRoutes()
}
}
How can I setup my root endpoint (/api/v1) in this case?
P.S. I've checked their docs, it says to use nested routes but I can't because I need to call routing in postRoutes() that breaks nested routes.
P.P.S. I am a noobie in Ktor and Kotlin.
You can either wrap the getPostsRoute() with the route("/api/v1") inside the postRoutes method or get rid of the postRoutes method and nest your routes inside the routing {}.
import io.ktor.application.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
fun main() {
embeddedServer(Netty, port = 5555, host = "0.0.0.0") {
postRoutes()
}.start(wait = false)
}
fun Route.getPostsRoute() {
get("/posts") {
call.respondText("Posts")
}
}
fun Application.postRoutes() {
routing {
route("/api/v1") {
getPostsRoute()
}
}
}

Kotlin SharedFlow - how to subscribe?

I have a JMS queue that produces messages. I want to share those messages with multiple Kotlin-consumers, but only if a Kotlin-consumer is connected. If a Kotlin-consumer is only active for 5 minutes, it should only receive messages within that window. The Kotlin-consumer should be able to subscribe at any point, and the received messages obtained at any time.
From reading the docs I think that Kotlin's SharedFlow is the best way to do that...
"SharedFlow is useful for broadcasting events that happen inside an application to subscribers that can come and go." (docs)
but I can't find any good examples, and the docs are very confusing. The SharedFlow docs say "all collectors get all emitted values", and "An active collector of a shared flow is called a subscriber" but it doesn't explain how to actually create a subscriber.
Options:
shareIn says it converts "a cold Flow into a hot SharedFlow", but I don't have a cold flow, I have a hot SharedFlow.
Flow.collect is linked in the docs, but it's marked as internal: "This is an internal kotlinx.coroutines API that should not be used from outside of kotlinx.coroutines."
launchIn is described as terminal - but I don't want to end consuming
amqMessageListener.messagesView.collect(object : FlowCollector<Message> { // internal API warning
override suspend fun emit(value: Message) { ... }
})
Both Flow.collect and launchIn "never complete normally" - but I do want to be able to complete them normally.
Here's how I've tried to subscribe to messages, but I can never get any results.
import kotlin.time.Duration
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.launch
suspend fun main() = coroutineScope {
produceMessages()
delay(1000)
}
suspend fun produceMessages() = coroutineScope {
val messages = MutableSharedFlow<Int>(
replay = 0,
extraBufferCapacity = 0,
onBufferOverflow = BufferOverflow.SUSPEND
)
// emit messages
launch {
repeat(100000) {
println("emitting $it - result:${messages.tryEmit(it)}")
delay(Duration.seconds(0.5))
}
}
println("waiting 3")
delay(Duration.seconds(3))
launch {
messages.onEach { println("onEach") }
}
launch {
messages.onEach { println("onEach") }.launchIn(CoroutineScope(Dispatchers.Default))
}
launch {
messages.collect { println("collect") }
}
launch {
messages.launchIn(this)
messages.collect { println("launchIn + collect") }
}
launch {
val new = messages.shareIn(this, SharingStarted.Eagerly, replay = Int.MAX_VALUE)
delay(Duration.seconds(2))
println("new.replayCache: ${new.replayCache}")
}
launch {
println("sharing")
val l = mutableListOf<Int>()
val x = messages.onEach { println("hello") }.launchIn(this)
repeat(1000) {
delay(Duration.seconds(1))
println("result $it: ${messages.replayCache}")
println("result $it: ${messages.subscriptionCount.value}")
println("result $it: ${l}")
}
}
}
Update
I have a working solution.Thanks go to Tenfour04 for their answer, which helped me understand.
Here's an example close to what I needed.
import kotlin.time.Duration
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.runningFold
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
class Publisher {
private val publishingScope = CoroutineScope(SupervisorJob())
private val messagesFlow = MutableSharedFlow<Int>(
replay = 0,
extraBufferCapacity = 0,
onBufferOverflow = BufferOverflow.SUSPEND
)
init {
// emit messages
publishingScope.launch {
repeat(100000) {
println("emitting $it")
messagesFlow.emit(it)
delay(Duration.seconds(0.5))
}
}
}
/** Create a new [SharedFlow] that receives all updates from [messagesFlow] */
fun listen(name: String): SharedFlow<Int> = runBlocking {
val listenerScope = CoroutineScope(SupervisorJob())
val capture = MutableSharedFlow<Int>(
replay = Int.MAX_VALUE,
extraBufferCapacity = 0,
onBufferOverflow = BufferOverflow.SUSPEND
)
messagesFlow
.onEach {
println("$name is getting message $it")
capture.emit(it)
}
.launchIn(listenerScope)
capture.asSharedFlow()
}
/** Create a new [StateFlow], which holds all accumulated values of [messagesFlow] */
suspend fun collectState(name: String): StateFlow<List<Int>> {
return messagesFlow
.runningFold(emptyList<Int>()) { acc, value ->
println("$name is getting message $value")
acc + value
}
.stateIn(publishingScope)
}
}
fun main() {
val publisher = Publisher()
// both Fish and Llama can subscribe at any point, and get all subsequent values
runBlocking {
delay(Duration.seconds(2))
launch {
val listenerFish = publisher.collectState("Fish")
repeat(4) {
println("$it. Fish replayCache ${listenerFish.value}")
delay(Duration.seconds(2))
}
}
delay(Duration.seconds(2))
launch {
val listenerLlama = publisher.listen("Llama")
repeat(4) {
println("$it. Llama replayCache" + listenerLlama.replayCache)
delay(Duration.seconds(2))
}
}
delay(Duration.seconds(10))
}
}
Flow.collect has an overload that is marked internal, but there is a public collect extension function that is very commonly used. I recommend putting this catch-all import at the top of your file, and then the extension function will be available among other Flow-related tasks: import kotlinx.coroutines.flow.*
launchIn and collect are the two most common ways to subscribe to a flow. They are both terminal. "Terminal" doesn't mean it ends consuming...it means it starts consuming! A "nonterminal" function is one that wraps a Flow in another Flow without starting to collect it.
"Never complete normally" means that the code following it in the coroutine will not be reached. collect subscribes to a flow and suspends the coroutine until the flow is complete. Since a SharedFlow never completes, it "never completes normally".
It's hard to comment on your code because it's unusual to start your flow and collect it within the same function. Typically a SharedFlow would be exposed as a property for use by other functions. By combining it all into a single function you're hiding the fact that typically a SharedFlow is possibly publishing from a different coroutine scope than its being collected from.
Here's an example partially adapted from your code:
class Publisher {
private val publishingScope = CoroutineScope(SupervisorJob())
val messagesFlow: SharedFlow<Int> = MutableSharedFlow<Int>(
replay = 0,
extraBufferCapacity = 0,
onBufferOverflow = BufferOverflow.SUSPEND
).also { flow ->
// emit messages
publishingScope.launch {
repeat(100000) {
println("emitting $it")
flow.emit(it)
delay(500)
}
}
}
}
fun main() {
val publisher = Publisher()
runBlocking {
val subscribingScope = CoroutineScope(SupervisorJob())
// Delay a while. We'll miss the first couple messages.
delay(1300)
// Subscribe to the shared flow
subscribingScope.launch {
publisher.messagesFlow.collect { println("I am colllecting message $it") }
// Any code below collection in this inner coroutine won't be reached because collect doesn't complete normally.
}
delay(3000) // Keep app alive for a while
}
}
Since collect typically prevents any code below it from running in the coroutine, the launchIn function can make it a little more obvious what's happening, and more concise:
fun main() {
val publisher = Publisher()
runBlocking {
val subscribingScope = CoroutineScope(SupervisorJob())
delay(1300)
publisher.messagesFlow.onEach { println("I am colllecting message $it") }
.launchIn(subscribingScope)
delay(3000)
}
}

how create a coroutine inside a Controller method in order to call a suspend function

Goal: my microservice must consume another Rest endpoint and I am trying to follow Coroutines (Async).
Here is how I coded in Service in order to consume another rest endpoint
Service
suspend fun getCoroutine(){
val someData = getData()
print(someData)
}
suspend fun getData(): String {
val client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.authenticator(Authenticator.getDefault())
.build();
val request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:3000/employees"))
.build();
val response = client.sendAsync(request, BodyHandlers.ofString());
return response.get().body() // suspend and return String not a Future
}
And I want "suspend fun getCoroutine()" method be called from my controller
package com.tolearn.endpoint
import com.tolearn.DemoGrpcKafkaReply
import com.tolearn.DemoGrpcKafkaRequest
import com.tolearn.DemoGrpcKafkaServiceGrpc
import com.tolearn.service.DemoService
import io.grpc.stub.StreamObserver
import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
#Singleton
class DemoEndpoint : DemoGrpcKafkaServiceGrpc.DemoGrpcKafkaServiceImplBase(){
#Inject
lateinit var demoService: DemoService
override fun send(request: DemoGrpcKafkaRequest?, responseObserver: StreamObserver<DemoGrpcKafkaReply>?) {
demoService.getCoroutine()
}
}
I am very new on Coroutine. I learned that a suspend function only can be evoked from anotehr suspend function or a Coroutine so, in my case I want to create a Coroutine. After googling I tried
override fun send(request: DemoGrpcKafkaRequest?, responseObserver: StreamObserver<DemoGrpcKafkaReply>?) {
val tryingCoroutine = runBlocking { demoService.getCoroutine() }
But runBlocking can't be resolved.
Also I tried based on docs reference and also lauch can't be resolved
override fun send(request: DemoGrpcKafkaRequest?, responseObserver: StreamObserver<DemoGrpcKafkaReply>?) {
launch( demoService.getCoroutine() ) { // not confined -- will work with main thread
}
Here is the build.gradle
plugins {
id("org.jetbrains.kotlin.jvm") version "1.4.10"
id("org.jetbrains.kotlin.kapt") version "1.4.10"
id("org.jetbrains.kotlin.plugin.allopen") version "1.4.10"
id("com.github.johnrengelman.shadow") version "6.1.0"
id("io.micronaut.application") version "1.2.0"
id("com.google.protobuf") version "0.8.13"
}
version = "0.1"
group = "com.tolearn"
repositories {
mavenLocal()
jcenter()
mavenCentral()
}
micronaut {
testRuntime("junit5")
processing {
incremental(true)
annotations("com.tolearn.*")
}
}
dependencies {
implementation("io.micronaut:micronaut-validation")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlinVersion}")
implementation("org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}")
implementation("io.micronaut.kotlin:micronaut-kotlin-runtime")
implementation("io.micronaut:micronaut-runtime")
implementation("io.micronaut.grpc:micronaut-grpc-runtime")
implementation("javax.annotation:javax.annotation-api")
implementation("io.micronaut.kafka:micronaut-kafka")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:'1.4.2")
runtimeOnly("ch.qos.logback:logback-classic")
runtimeOnly("com.fasterxml.jackson.module:jackson-module-kotlin")
testImplementation("io.micronaut:micronaut-http-client")
}
application {
mainClass.set("com.tolearn.ApplicationKt")
}
java {
sourceCompatibility = JavaVersion.toVersion("11")
}
tasks {
compileKotlin {
kotlinOptions {
jvmTarget = "11"
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = "11"
}
}
}
sourceSets {
main {
java {
srcDirs("build/generated/source/proto/main/grpc")
//srcDirs 'build/generated/source/proto/main/grpckt'
srcDirs("build/generated/source/proto/main/java")
}
}
}
protobuf {
protoc { artifact = "com.google.protobuf:protoc:3.14.0" }
plugins {
grpc { artifact = "io.grpc:protoc-gen-grpc-java:1.33.1" }
//grpckt { artifact = "io.grpc:protoc-gen-grpc-kotlin:1.0.0" }
}
generateProtoTasks {
all()*.plugins {
grpc {}
//grpckt {}
}
}
}
MAIN QUESTION: what I have to do to call a suspend function from the method of my Controller? A secondary question, am I doing some weird approach trying to call a suspend function from a Controller method? Am I wrong trying to take advantage of Coroutine in this case?
*** edit 1
val tryingCoroutine = runBlocking {
coroutineScope { // Creates a coroutine scope
launch {
demoService.getCoroutine()
println("Task from nested launch")
}
}
}
println(tryingCoroutine.isCompleted)
You need to add kotlinx-coroutines-core dependency to resolve scopes(runBlocking/launch).
Here is the link to maven repo:
https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core
Scope documentation:
https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
Once you have scopes added to your project you should be able to run suspended functions within non-suspending block.Also, you making use CoroutineExceptionHandler to handle errors.
https://kotlinlang.org/docs/reference/coroutines/exception-handling.html#coroutineexceptionhandler
Technically, the controller should handoff any long-running operations to another thread and return the appropriate response. So you are doing anything weird.

Creating the actor from inside coroutineScope is blocking the thread, but the same actor created as extension function of CoroutineScope is not

I am trying to play with actor builder construct in kotlin. i have written the code below to send and receive a message from actor.
package com.byteobject.prototype.kotlin
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.actor
import kotlinx.coroutines.channels.consumeEach
class GreetingsMessage(val to: String, val greetings: CompletableDeferred<String>)
fun CoroutineScope.newGreeter(greet: String) = actor<GreetingsMessage> {
channel.consumeEach {
it.greetings.complete("$greet ${it.to}")
}
}
fun main() {
runBlocking {
val greeter = newGreeter("Hello")
val greetingsMessage = GreetingsMessage("World", CompletableDeferred())
launch(Dispatchers.Default) {
greeter.send(greetingsMessage)
}
launch(Dispatchers.Default) {
println(greetingsMessage.greetings.await())
greeter.close()
}
}
}
this code works as expected.
but the code below is not, as it is hanging the program.
package com.byteobject.prototype.kotlin
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.actor
import kotlinx.coroutines.channels.consumeEach
class GreetingsMessage(val to: String, val greetings: CompletableDeferred<String>)
suspend fun newGreeter(greet: String) = coroutineScope {
actor<GreetingsMessage> {
channel.consumeEach {
it.greetings.complete("$greet ${it.to}")
}
}
}
fun main() {
runBlocking {
val greeter = newGreeter("Hello")
val greetingsMessage = GreetingsMessage("World", CompletableDeferred())
launch(Dispatchers.Default) {
greeter.send(greetingsMessage)
}
launch(Dispatchers.Default) {
println(greetingsMessage.greetings.await())
greeter.close()
}
}
}
with the slight modification to the code by making newGreeter function as suspending function and enclosing the function by coroutineScope the call to newGreeter method is blocking the thread and its hanging the program. I believe newGreeter as an extension function to CoroutineScope and as a suspending function enclosed inside coroutineScope should work exactly the same.
I want to know the difference between the two approach and why the second approach is hanging the program.
I tried the same thing with produce function and here also i found the call to suspend function to get ReceieveChannel is blocking the thread, where as the same produce construct used as an extension function is working as expected
this code is non blocking
package com.byteobject.prototype.kotlin
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.produce
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
fun CoroutineScope.produceIntegers(n: Int) = produce<Int> {
for (i in 1..n)
send(i)
close()
}
fun main() {
runBlocking {
val intChan = produceIntegers(10)
launch {
for (i in intChan)
println(i)
}
}
}
where as this is blocking on the call to produceIntegers method
package com.byteobject.prototype.kotlin
import kotlinx.coroutines.channels.produce
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
suspend fun produceIntegers(n: Int) = coroutineScope {
produce<Int> {
for (i in 1..n)
send(i)
close()
}
}
fun main() {
runBlocking {
val intChan = produceIntegers(10)
launch {
for (i in intChan)
println(i)
}
}
}
The problem is that coroutineScope { } creates a new blocking scope (structured concurrency) - waits until all launched coroutines have completed.
See: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-scope.html
this function returns as soon as the given block and all its children coroutines are completed.
On the other side, the extension function just uses the coroutinescope that is in the context (receiver).