Debug logging from dispatcher does not come to main thread - kotlin

I am trying to use Kotlin to utilize more asynchronous task processing for our service that is consuming from a message queue and doing some DB relate tasks and publishing to another message queue. I am trying to understand better how scoping works when we need to pass certain values to do sequential work vs parallel work.
I copied some test scripts from another Github and for testing and trying to run it to understand some fundamentals and concepts.
I used this example and trying to do parallel tasks
fun `testing async scope`() = runTest {
val list = listOf(Pair("name1", 1000L), Pair("name2", 1000L), Pair("name3", 1000L), Pair("name4", 1000L),
Pair("name5", 1000L), Pair("name6", 1000L))
val startTime = System.currentTimeMillis()
val parentJob = CoroutineScope(IO).launch {
launch { nameRun()}
launch { employerRun() }
}
parentJob.invokeOnCompletion {
println("Finishing parentJob in Thread: ${Thread.currentThread().name} in ${System.currentTimeMillis() - startTime}")
}
}
private suspend fun nameRun() {
val listEmployer = listOf(Pair("name1", 1000L), Pair("name2", 1000L), Pair("name3", 1000L), Pair("name4", 1000L),
Pair("name5", 1000L), Pair("name6", 1000L))
coroutineScope {
val startTime = System.currentTimeMillis()
println("Launching nameRun in Thread: ${Thread.currentThread().name} at $startTime")
launch {
val results = listEmployer.map {
async {
process(it)
}
}.awaitAll() // List<B>
println(results)
}
println("Finishing nameRun in Thread: ${Thread.currentThread().name} in ${System.currentTimeMillis() - startTime}")
}
}
private suspend fun employerRun() {
val listEmployer = listOf(Pair("employer1", 1000L), Pair("employer2", 1000L), Pair("employer3", 1000L), Pair("employer4", 1000L),
Pair("employer5", 1000L), Pair("employer6", 1000L))
coroutineScope {
val startTime = System.currentTimeMillis()
println("Launching employerRun in Thread: ${Thread.currentThread().name} at $startTime")
launch {
val results = listEmployer.map {
async {
process(it)
}
}.awaitAll() // List<B>
println(results)
}
println("Finishing employerRun in Thread: ${Thread.currentThread().name} in ${System.currentTimeMillis() - startTime}")
}
}
private suspend fun process(pair : Pair<String, Long>): Boolean {
delay(pair.second * 10)
println(pair.first)
return true
}
But printing only prints these lines.
Launching employerRun in Thread: DefaultDispatcher-worker-1 #coroutine#4 at 1648941528859
Launching nameRun in Thread: DefaultDispatcher-worker-3 #coroutine#3 at 1648941528859
Finishing employerRun in Thread: DefaultDispatcher-worker-1 #coroutine#4 in 3
Finishing nameRun in Thread: DefaultDispatcher-worker-3 #coroutine#3 in 3
Is there some limitation of the scope within the IO that does not print all the logs the moment it enters a suspend function? Even then, there is new print to show the job was completed?
I am running it with -ea
What I want to do is able to basically parallel everything here
Scope Parent (Parallel) -> nameRun Scope (Parallel)
-> name1 compute
-> ...
-> name6 compute
-> employerRun Scope (Parallel)
-> employer1 compute
-> ...
-> employer6 compute
Seems like we can only get logs from Parent scope -> nameRun or employerRun, but we cant get logs from the deeper scope from employerRun/nameRun
Could it be the difference also in using coroutineScope {...} vs using CoroutineScope(IO).launch { ... }
EDIT:
Seems to be related to this Difference between CoroutineScope and coroutineScope in Kotlin , and impact is due to this
val parentJob = CoroutineScope(IO).launch {
launch { nameRun()}
launch { employerRun() }
}
with the way it is structure, I would want to use
coroutineScope {
launch { nameRun() }
launch { employerRun() }
}
instead. And the scoping inside of nameRun() and employerRun() should utilize coroutineScope as well to ensure we get the required result instead of suspending it. But it seems like however, this method is actually not running in parallel

Finally figured it out -
I forgot to use join() if I want to explicitly declare that I want a result from the launch { }. Otherwise it just fires and forgets. And I have to also set the async {} for my code to do perform asynchronous work or else launch is sequential.
CoroutineScope(IO).launch {
val results = list.map {
async(IO) {
val subStart = System.currentTimeMillis()
println("Launching async in Thread: ${Thread.currentThread().name} at $subStart with element ${it.first}")
process(it)
val end = System.currentTimeMillis()
println("Finishing async in Thread: ${Thread.currentThread().name} at ${end} in ${end - subStart} with element ${it.first}")
}
}
val results2 = listEmployer.map {
async(IO) {
val subStart = System.currentTimeMillis()
println("Launching async in Thread: ${Thread.currentThread().name} at $subStart with element ${it.first}")
process(it)
val end = System.currentTimeMillis()
println("Finishing async in Thread: ${Thread.currentThread().name} at ${end} in ${end - subStart} with element ${it.first}")
}
}
println(results.awaitAll() + results2.awaitAll())
}.join()
Result:
Starting parentJob in Thread: main #coroutine#1 in 1648952427825
Launching async in Thread: DefaultDispatcher-worker-3 #coroutine#3 at 1648952427838 with element name1
Launching async in Thread: DefaultDispatcher-worker-4 #coroutine#4 at 1648952427841 with element name2
Launching async in Thread: DefaultDispatcher-worker-6 #coroutine#5 at 1648952427842 with element name3
Launching async in Thread: DefaultDispatcher-worker-2 #coroutine#6 at 1648952427842 with element name4
Launching async in Thread: DefaultDispatcher-worker-3 #coroutine#7 at 1648952427843 with element name5
Launching async in Thread: DefaultDispatcher-worker-6 #coroutine#8 at 1648952427843 with element name6
Launching async in Thread: DefaultDispatcher-worker-8 #coroutine#9 at 1648952427843 with element employer1
Launching async in Thread: DefaultDispatcher-worker-12 #coroutine#12 at 1648952427844 with element employer4
Launching async in Thread: DefaultDispatcher-worker-11 #coroutine#11 at 1648952427844 with element employer3
Launching async in Thread: DefaultDispatcher-worker-6 #coroutine#10 at 1648952427843 with element employer2
Launching async in Thread: DefaultDispatcher-worker-4 #coroutine#13 at 1648952427844 with element employer5
Launching async in Thread: DefaultDispatcher-worker-8 #coroutine#14 at 1648952427844 with element employer6
name1
name2
name3
name4
name5
name6
employer1
Finishing async in Thread: DefaultDispatcher-worker-1 #coroutine#3 at 1648952428846 in 1008 with element name1
Finishing async in Thread: DefaultDispatcher-worker-4 #coroutine#6 at 1648952428846 in 1004 with element name4
Finishing async in Thread: DefaultDispatcher-worker-8 #coroutine#4 at 1648952428846 in 1005 with element name2
Finishing async in Thread: DefaultDispatcher-worker-10 #coroutine#9 at 1648952428846 in 1003 with element employer1
Finishing async in Thread: DefaultDispatcher-worker-12 #coroutine#8 at 1648952428846 in 1003 with element name6
Finishing async in Thread: DefaultDispatcher-worker-11 #coroutine#7 at 1648952428846 in 1003 with element name5
Finishing async in Thread: DefaultDispatcher-worker-6 #coroutine#5 at 1648952428846 in 1004 with element name3
employer4
employer3
Finishing async in Thread: DefaultDispatcher-worker-9 #coroutine#12 at 1648952428847 in 1003 with element employer4
employer6
employer5
employer2
Finishing async in Thread: DefaultDispatcher-worker-5 #coroutine#13 at 1648952428848 in 1004 with element employer5
Finishing async in Thread: DefaultDispatcher-worker-1 #coroutine#14 at 1648952428847 in 1003 with element employer6
Finishing async in Thread: DefaultDispatcher-worker-3 #coroutine#11 at 1648952428847 in 1003 with element employer3
Finishing async in Thread: DefaultDispatcher-worker-7 #coroutine#10 at 1648952428848 in 1005 with element employer2
[kotlin.Unit, kotlin.Unit, kotlin.Unit, kotlin.Unit, kotlin.Unit, kotlin.Unit, kotlin.Unit, kotlin.Unit, kotlin.Unit, kotlin.Unit, kotlin.Unit, kotlin.Unit]

Related

Will the collect of the Flow block to execute?

I run the Code A and get the Result A. In my mind, it should be the Result B.
It seems that flow.collect { value -> println(value) } block to execute.
Will the collect of the Flow block to execute ?
Code A
fun simple(): Flow<Int> = flow {
println("Flow started")
for (i in 1..3) {
delay(300)
emit(i)
}
}
fun main() = runBlocking<Unit> {
println("Calling simple function...")
val flow = simple()
println("Calling collect...")
flow.collect { value -> println(value) } //Block?
println("Calling collect again...")
}
Result A
Calling simple function...
Calling collect...
Flow started
1
2
3
Calling collect again...
Result B
Calling simple function...
Calling collect...
Flow started
Calling collect again...
1
2
3
BTW, I run Code 1 and get the result 1 as I expected.
Code 1
fun simple(): Flow<Int> = flow {
for (i in 1..3) {
delay(100)
emit(i)
}
}
fun main() = runBlocking<Unit> {
launch {
for (k in 1..3) {
println("I'm not blocked $k")
delay(100)
}
}
simple().collect { value -> println(value) }
}
Result 1
I'm not blocked 1
1
I'm not blocked 2
2
I'm not blocked 3
3
Suspend functions do not block, but they are synchronous, meaning the execution of the code in the coroutine waits for the suspend function to return before continuing. The difference between suspend function calls and blocking function calls is that the thread is released to be used for other tasks while the coroutine is waiting for the suspend function to return.
collect is a suspend function that internally calls its lambda repeatedly and synchronously (suspending, not blocking) and doesn't return until the Flow is completed.
launch is an asynchronous function that starts a coroutine. It returns immediately without waiting for its coroutine to complete, which is why Code 1 behaved as you expected.

delay() block its coroutine when using thread pool as the dispatcher?

Play with Kotlin coroutine, I am confused by the results of my toy code:
import kotlinx.coroutines.*
import java.util.concurrent.Executors
suspend fun task(sleepTime: Long) {
println("start task in Thread ${Thread.currentThread()}")
delay(sleepTime)
//yield()
println("end task with context in Thread ${Thread.currentThread()}")
}
println("start")
runBlocking {
val handler = CoroutineExceptionHandler() { context, ex ->
println("Caught: $context | ${ex.message}")
}
Executors.newFixedThreadPool(2)
.asCoroutineDispatcher().use { dispatcher ->
launch(dispatcher + handler) { task(100) }
launch(Dispatchers.IO + handler) { task(1000) }
println("called tasks ${Thread.currentThread()}")
}
}
println("done")
output:
start
start task in Thread Thread[pool-1-thread-1 #coroutine#2,5,main]
called tasks Thread[main #coroutine#1,5,main]
start task in Thread Thread[DefaultDispatcher-worker-2 #coroutine#3,5,main]
end task with context in Thread Thread[DefaultDispatcher-worker-2 #coroutine#3,5,main]
done
My question is why coroutine#1 does not continue? If I change delay() to yield(), it worked:
start
start task in Thread Thread[pool-1-thread-1 #coroutine#2,5,main]
end task with context in Thread Thread[pool-1-thread-2 #coroutine#2,5,main]
called tasks Thread[main #coroutine#1,5,main]
start task in Thread Thread[DefaultDispatcher-worker-1 #coroutine#3,5,main]
end task with context in Thread Thread[DefaultDispatcher-worker-1 #coroutine#3,5,main]
done
Closeable.use executes the code inside it and then closes the Closable, in this case your dispatcher. Since launched coroutines are run asynchronously, use doesn't wait for them to finish, and shuts down the dispatcher right away, thereby cancelling your coroutines. It's possible yield() happens so quickly that they have time to finish before getting cancelled.
If you move your use block outside the runBlocking block, then it waits for them to finish.
fun main() {
println("start")
Executors.newFixedThreadPool(2)
.asCoroutineDispatcher().use { dispatcher ->
runBlocking {
val handler = CoroutineExceptionHandler() { context, ex ->
println("Caught: $context | ${ex.message}")
}
launch(dispatcher + handler) { task(100) }
launch(Dispatchers.IO + handler) { task(1000) }
println("called tasks ${Thread.currentThread()}")
}
}
println("done")
}

NonCancellable coroutine gets cancelled

I'm trying to experiment with non-cancellable coroutines and I wrote the following code:
fun main(): Unit = runBlocking {
// launch first coroutine
coroutineScope {
val job1 = launch {
withContext(NonCancellable) {
val delay = Random.nextLong(from = 500, until = 5000)
println("Coroutine started. Waiting for ${delay}ms")
delay(delay)
println("Coroutine completed")
}
}
delay(300) // let it print the first line
println("Cancelling coroutine")
job1.cancelAndJoin()
}
}
Output:
Coroutine started. Waiting for 1313ms
Cancelling coroutine
Coroutine completed
So far, everything works as expected. However, if I pass the NonCancellable context (or rather, Job) directly in the launch function, the behaviour changes and the coroutine is cancelled:
fun main(): Unit = runBlocking {
// launch first coroutine
coroutineScope {
val job1 = launch(context = NonCancellable) {
val delay = Random.nextLong(from = 500, until = 5000)
println("Coroutine started. Waiting for ${delay}ms")
delay(delay)
println("Coroutine completed")
}
delay(300) // let it print the first line
println("Cancelling coroutine")
job1.cancelAndJoin()
}
}
Output:
Coroutine started. Waiting for 4996ms
Cancelling coroutine
Why is the second snippet producing a different output?
The job you pass as an argument to a launch method is not the job of the launched coroutine, but its parent job.
In the second snippet above, NonCancellable is the parent job of the job1. As the job1 is just a normal job, it's cancellable (but its parent isn't).

Does async block when using await

In the following code two asyncs are run:
import kotlinx.coroutines.*
import kotlin.system.*
fun main() = runBlocking<Unit> {
val time = measureTimeMillis {
val one = async { doSomethingUsefulOne() }
val two = async { doSomethingUsefulTwo() }
two.await()
one.await()
println("Finished")
}
}
suspend fun doSomethingUsefulOne(): Int {
delay(3000L)
println("first")
return 13
}
suspend fun doSomethingUsefulTwo(): Int {
delay(1000L)
println("second")
return 29
}
Why is "Finished" not printed first? The docs says this about await:
Awaits for completion of this value without blocking a thread
The complete quote is:
Awaits for completion of this value without blocking a thread and
resumes when deferred computation is complete, returning the resulting
value or throwing the corresponding exception if the deferred was
cancelled.
Source
without blocking a thread:
The execution inside other async coroutine is not blocked. Track the time and you'll see that there was no 'pause' in time execution.
resumes when deferred computation is complete: the main thread will wait to continue on following code (println("Finished")), since you force it by calling await() to wait for the returned value. But the executions inside coroutines are not blocked.
EDIT
Considering the comments, I agree with AndroidDev on misleading documentation. It should be changed to:
Awaits for completion of this value without blocking other coroutines or
threads...
or
Awaits for completion of this value blocking only the current thread ...
By slightly modifying the code, one will gain some insights. Let's add the elapsed time and the current thread to the print statements to see, what's going on:
Execute on kotlin playground
import kotlinx.coroutines.*
import kotlin.system.*
private val started = System.currentTimeMillis()
private fun debug(s: String) {
val elapsed = System.currentTimeMillis() - started
println("[$elapsed] $s -- ${Thread.currentThread()}")
}
fun main() = runBlocking<Unit> {
val time = measureTimeMillis {
val one = async { doSomethingUsefulOne() }
val two = async { doSomethingUsefulTwo() }
debug("awaiting")
two.await()
one.await()
debug("Finished")
}
debug("time = $time")
}
suspend fun doSomethingUsefulOne(): Int {
delay(3000L)
debug("first")
return 13
}
suspend fun doSomethingUsefulTwo(): Int {
delay(1000L)
debug("second")
return 29
}
When run with -Dkotlinx.coroutines.debug one should see something similar to
[46] awaiting -- Thread[main #coroutine#1,5,main]
[1056] second -- Thread[main #coroutine#3,5,main]
[3054] first -- Thread[main #coroutine#2,5,main]
[3054] Finished -- Thread[main #coroutine#1,5,main]
[3054] time = 3013 -- Thread[main #coroutine#1,5,main]
There is only one single thread.
If await had blocked the thread, the program could not have terminated.

Kotlin coroutines barrier: Wait for all coroutines to finish

I need to start many coroutines in a for loop and get a callback in the Main thread after all the tasks are completed.
What is the best way to do this?
//Main thread
fun foo(){
messageRepo.getMessages().forEach {message->
GlobalScope.launch {
doHardWork(message)
}
}
// here I need some callback to the Main thread that all work is done.
}
And there is no variant to iterate messages in CoroutineScope. The iteration must be done in the Main thread.
You can wait until all tasks will be completed with awaitAll and then execute your callback in the main thread with withContext
fun foo() {
viewModelScope.launch {
messageRepo.getMessages().map { message ->
viewModelScope.async(Dispatchers.IO) {
doHardWork(message)
}
}.awaitAll()
withContext(Dispatchers.Main) {
// here I need some callback that all work is done.
}
}
}