kotlin, why coroutines launch block only runs one function in it when a DataStore is used in the function, - kotlin-coroutines

Having a coroutines launch to execute three functions.
scope.launch {
getPrefsOne()
getPrefsTwo()
getPrefsThree()
}
If the function is doing something other than accessing DataStore, all three functions are completed as expected. But if the function is accessing the DataStore,
private fun getPrefValueFromDataStore(key: String): Flow<Any> {
val prefsKey = stringPreferencesKey(key)
var dataStore: DataStore<Preferences> = dataStore
val value = dataStore.data.map { preferences ->
preferences[prefsKey] ?: false
}
return value
}
then only the first function is called.
But if put three functions in their own launch block, all three are executed.
fun theLauncher() {
scope.launch {
getPrefsOne()
}
scope.launch {
getPrefsTwo()
}
scope.launch {
getPrefsThree()
}
}
Why, and how to run the three functions in one launch block?
The testing code:
import android.util.Log
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.emptyPreferences
import androidx.datastore.preferences.core.stringPreferencesKey
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.map
import java.io.IOException
class TestPrefs(val dataStore: DataStore<Preferences>) {
private var isPrefsOneEnabled = MutableStateFlow(false)
private var isPrefsTwoEnabled = MutableStateFlow(false)
private var isPrefsThreeEnabled = MutableStateFlow(false)
val job = SupervisorJob()
val scope = CoroutineScope(Dispatchers.IO + job)
private fun getPrefValueFromDataStore(key: String): Flow<Any> {
val prefsKey = stringPreferencesKey(key)
var dataStore: DataStore<Preferences> = dataStore
val value = dataStore.data.catch { exception ->
// dataStore.data throws an IOException when an error is encountered when reading data
println("+++ !!! exp $exception in getPrefValueFromDataStore($key)")
if (exception is IOException) {
Log.e("+++", "+++ !!! Error reading preferences.", exception)
emit(emptyPreferences())
} else {
throw exception
}
}
.map { preferences ->
preferences[prefsKey] ?: false
}
return value
.also{
println("+++ 111 --- exit getPrefValueFromDataStore($key), ret: $it")
}
}
fun theLauncher() {
System.out.println("+++ ### enter theLauncher() Thread: ${Thread.currentThread().id}")
scope.launch {
getPrefsOne()
getPrefsTwo()
getPrefsThree()
}
// scope.launch {
// getPrefsTwo()
// }
// scope.launch {
// getPrefsThree()
// }
System.out.println("+++ --- exit theLauncher() Thread: ${Thread.currentThread().id}")
}
suspend fun getPrefsOne() {
System.out.println("+++ +++ +++ getPrefsOne() Thread: ${Thread.currentThread().id}")
getPrefValueFromDataStore("TEST_PREFS_KEY_1")
.collect {
println("+++ getPrefsOne111111() getPrefValueFromDataStore().collect println got $it")
isPrefsOneEnabled.value = it as Boolean
}
//}
}
suspend fun getPrefsTwo() {
System.out.println("+++ +++ +++ getPrefsTwo() Thread: ${Thread.currentThread().id}")
getPrefValueFromDataStore("TEST_PREFS_KEY_2")
.collect {
println("+++ getPrefsTwo222222() getPrefValueFromDataStore().collect println got $it")
isPrefsTwoEnabled.value = it as Boolean
}
}
suspend fun getPrefsThree() {
System.out.println("+++ +++ +++ getPrefsThree Thread: ${Thread.currentThread().id}")
getPrefValueFromDataStore("TEST_PREFS_KEY_3")
.collect {
println("+++ getPrefsTwo3333333() getPrefValueFromDataStore().collect println got $it")
isPrefsThreeEnabled.value = it as Boolean
}
}
}
calling it and the log for two cases:
val dataStore: DataStore<Preferences> by preferencesDataStore(name = USER_PREFERENCES_NAME, scope = scope)
override fun onCreate(savedInstanceState: Bundle?) {
... ...
val testPrefs = TestPrefs(dataStore)
testPrefs.theLauncher()
}
1. run three functions in its own launch block, all three functions are called:
+++ ### enter theLauncher() Thread: 2
+++ +++ +++ getPrefsOne() Thread: 14581
+++ +++ +++ getPrefsTwo() Thread: 14583:
+++ --- exit theLauncher() Thread: 2
+++ 111 --- exit getPrefValueFromDataStore(TEST_PREFS_KEY_1), ret: TestPrefs$getPrefValueFromDataStore$$inlined$map$1#e6b5b99
+++ 111 --- exit getPrefValueFromDataStore(TEST_PREFS_KEY_2), ret: TestPrefs$getPrefValueFromDataStore$$inlined$map$1#af4ce5e
+++ +++ +++ getPrefsThree Thread: 14582
+++ 111 --- exit getPrefValueFromDataStore(TEST_PREFS_KEY_3), ret: TestPrefs$getPrefValueFromDataStore$$inlined$map$1#9e5470c
+++ getPrefsTwo3333333() getPrefValueFromDataStore().collect println got false
+++ getPrefsTwo222222() getPrefValueFromDataStore().collect println got false
+++ getPrefsOne111111() getPrefValueFromDataStore().collect println got false
2. run three functions in one launch block, only first function is called
+++ ### enter theLauncher() Thread: 2
+++ --- exit theLauncher() Thread: 2
+++ +++ +++ getPrefsOne() Thread: 14611
+++ 111 --- exit getPrefValueFromDataStore(TEST_PREFS_KEY_1), ret: TestPrefs$getPrefValueFromDataStore$$inlined$map$1#9e5470c
+++ getPrefsOne111111() getPrefValueFromDataStore().collect println got false
Update:
#Joffery answered the question with details and the StateFlow's doc link.
Here is some other post may help if anyone runs into similar question:
A StateFlow never completes, so collecting it is an infinite action. This is explained in the documentation of StateFlow. Coroutines are sequential, so if you call collect on a StateFlow, none of the code after that call in the coroutine will ever be reached.

You're not getting single values here. You're collecting infinite flows. The point of a flow is not to get one asynchronous value but rather get updates. In the case of DataStore, I believe it returns an infinite flow which gives you all further updates to the preference.
This means that .collect { ... } on such a flow will suspend indefinitely.
When you run a single launch, you're running a single coroutine that executes each function sequentially. The first needs to complete before the other ones are executed. But the first one is stuck on an infinite collection, so it appears as if it hangs.
If your goal is indeed to have 3 concurrent (and infinite) collections of events which maintain those isPrefsXEnabled boolean flows, then they have to be in separate coroutines, because that's how you would express this concurrency.

Related

Kotlin coroutines within a Spring REST endpoint

Given a REST endpoint and two asynchronous coroutines each returning an integer, I want this endpoint to return their sum. The two functions (funA and funB) should run in parallel, in such a way that the whole computation should take ~3secs.
I'm using SpringBoot 2.6.3 and Kotlin Coroutines 1.6.0. Here is my attempt:
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
#RestController
class Controllers {
#GetMapping(
value = ["/summing"]
)
fun summing(): String {
val finalResult = runBlocking {
val result = async { sum() }
println("Your result: ${result.await()}")
return#runBlocking result
}
println("Final Result: $finalResult")
return "$finalResult"
}
suspend fun funA(): Int {
delay(3000)
return 10
}
suspend fun funB(): Int {
delay(2000)
return 90
}
fun sum() = runBlocking {
val resultSum = async { funA().await() + funB().await() }
return#runBlocking resultSum
}
}
The problem is that this code does not compile as await() is not recognised as a valid method. If I remove await() the two functions are executed in series (total time ~5 secs) and instead of the expected result (100), I get:
Your result: DeferredCoroutine{Completed}#1c1fe804
Final Result: DeferredCoroutine{Completed}#48a622de
and so the endpoint returns "DeferredCoroutine{Completed}#48a622de".
I want the endpoint to return "100" instead and within ~3secs. How can I achieve this?
You really messed this up ;-) There are several problems with your code:
Use runBlocking() only to use another runBlocking() inside it.
Use async() and immediately call await() on it (in summing()) - it does nothing.
funA().await() and funB().await() don't make any sense really. These functions return integers, you can't await() on already acquired integers.
Generally, using much more code than needed.
The solution is pretty simple: use runBlocking() once to jump into coroutine world and then use async() to start both functions concurrently to each other:
runBlocking {
val a = async { funA() }
val b = async { funB() }
a.await() + b.await()
}
Or alternatively:
runBlocking {
listOf(
async { funA() },
async { funB() },
).awaitAll().sum()
}
Or (a little shorter, but I consider this less readable):
runBlocking {
val a = async { funA() }
funB() + a.await()
}
Also,runBlocking() is not ideal. I believe Spring has support for coroutines, so it would be better to make summing() function suspend and use coroutineScope() instead of runBlocking() - this way the code won't block any threads.

Exception in invokeOnCancellation handler for CancellableContinuation

i have a suspendCancellableCoroutine as an animation listener extension, but its crashing on api 30, heres the extension
suspend fun Animator.startAndWait() = suspendCancellableCoroutine<Unit> { continuation ->
continuation.invokeOnCancellation { cancel() }
this.addListener(object : AnimatorListenerAdapter() {
private var endedSuccessfully = true
override fun onAnimationCancel(animation: Animator?) {
endedSuccessfully = false
}
override fun onAnimationStart(animation: Animator?) {
animation?.removeListener(this)
if (continuation.isActive) {
// If the coroutine is still active...
if (endedSuccessfully) {
// ...and the Animator ended successfully, resume the coroutine
continuation.resume(Unit)
} else {
// ...and the Animator was cancelled, cancel the coroutine too
continuation.cancel()
}
}
}
})
start()
}
I'm calling it from the run method of another animator
private fun createRevealAnimation() {
val x: Int = binding.root.right / 2
val y: Int = binding.root.bottom - binding.root.bottom / 9
val endRadius = hypot(binding.root.width.toDouble(), binding.root.height.toDouble()).toInt()
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Default) {
createCircleRevealAnimator(x, y, endRadius).run {
duration = 250
withContext(Dispatchers.Main){
binding.pokeballOpen.visibility = View.VISIBLE
}
startAndWait()
navigateToListFragment()
}
}
}
private fun createCircleRevealAnimator(x: Int, y: Int, endRadius: Int): Animator {
return ViewAnimationUtils.createCircularReveal(
binding.pokeballOpen, x, y,
0f,
endRadius.toFloat()
)
}
it works fine on every version up until api 30 wheres it crashes and throws this error
2021-02-06 17:50:42.205 29495-29590/com.sealstudios.pokemonApp.paid E/AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-1
Process: com.sealstudios.pokemonApp.paid, PID: 29495
kotlinx.coroutines.CompletionHandlerException: Exception in invokeOnCancellation handler for CancellableContinuation(DispatchedContinuation[Dispatchers.Default, Continuation at com.sealstudios.pokemonApp.ui.SplashScreenFragment$createRevealAnimation$1.invokeSuspend(SplashScreenFragment.kt:61)#472a1ed]){CancelledContinuation[kotlinx.coroutines.JobCancellationException: StandaloneCoroutine is cancelling; job=StandaloneCoroutine{Cancelling}#c735822]}#e4312b3
at kotlinx.coroutines.CancellableContinuationImpl.callCancelHandler(CancellableContinuationImpl.kt:583)
at kotlinx.coroutines.CancellableContinuationImpl.cancel(CancellableContinuationImpl.kt:195)
at kotlinx.coroutines.CancellableContinuationImpl.parentCancelled$kotlinx_coroutines_core(CancellableContinuationImpl.kt:205)
at kotlinx.coroutines.ChildContinuation.invoke(JobSupport.kt:1484)
at kotlinx.coroutines.JobSupport.notifyCancelling(JobSupport.kt:1511)
at kotlinx.coroutines.JobSupport.tryMakeCompletingSlowPath(JobSupport.kt:897)
at kotlinx.coroutines.JobSupport.tryMakeCompleting(JobSupport.kt:860)
at kotlinx.coroutines.JobSupport.makeCompletingOnce$kotlinx_coroutines_core(JobSupport.kt:825)
at kotlinx.coroutines.AbstractCoroutine.resumeWith(AbstractCoroutine.kt:111)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
Caused by: java.lang.IllegalStateException: The current thread must have a looper!
at android.view.Choreographer$1.initialValue(Choreographer.java:111)
at android.view.Choreographer$1.initialValue(Choreographer.java:106)
at java.lang.ThreadLocal.setInitialValue(ThreadLocal.java:180)
at java.lang.ThreadLocal.get(ThreadLocal.java:170)
at android.view.Choreographer.getInstance(Choreographer.java:288)
at android.graphics.animation.RenderNodeAnimator$DelayedAnimationHelper.<init>(RenderNodeAnimator.java:431)
at android.graphics.animation.RenderNodeAnimator.getHelper(RenderNodeAnimator.java:415)
at android.graphics.animation.RenderNodeAnimator.cancel(RenderNodeAnimator.java:226)
at com.sealstudios.pokemonApp.ui.listenerExtensions.AnimatorListenersKt$startAndWait$$inlined$suspendCancellableCoroutine$lambda$1.invoke(AnimatorListeners.kt:12)
at com.sealstudios.pokemonApp.ui.listenerExtensions.AnimatorListenersKt$startAndWait$$inlined$suspendCancellableCoroutine$lambda$1.invoke(Unknown Source:2)
at kotlinx.coroutines.InvokeOnCancel.invoke(CancellableContinuationImpl.kt:539)
at kotlinx.coroutines.CancellableContinuationImpl.callCancelHandler(CancellableContinuationImpl.kt:230)
at kotlinx.coroutines.CancellableContinuationImpl.cancel(CancellableContinuationImpl.kt:195) 
at kotlinx.coroutines.CancellableContinuationImpl.parentCancelled$kotlinx_coroutines_core(CancellableContinuationImpl.kt:205) 
at kotlinx.coroutines.ChildContinuation.invoke(JobSupport.kt:1484) 
at kotlinx.coroutines.JobSupport.notifyCancelling(JobSupport.kt:1511) 
at kotlinx.coroutines.JobSupport.tryMakeCompletingSlowPath(JobSupport.kt:897) 
at kotlinx.coroutines.JobSupport.tryMakeCompleting(JobSupport.kt:860) 
at kotlinx.coroutines.JobSupport.makeCompletingOnce$kotlinx_coroutines_core(JobSupport.kt:825) 
at kotlinx.coroutines.AbstractCoroutine.resumeWith(AbstractCoroutine.kt:111) 
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46) 
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106) 
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571) 
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738) 
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678) 
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665) 
any one have any ideas I'm pretty lost with this one.
Remove Dispatchers.Default
viewLifecycleOwner.lifecycleScope.launch { ... }

is this right to use coroutines in a non coroutine context

Having a Processor class, trying to replace some of the code with coroutines. Since it is in a non coroutines context so val serviceScope = CoroutineScope(Dispatchers.IO + serviceJob) is added and used for start coroutines.
Added CoroutineScope, and using serviceScope.launch{} in the place which was using Thread{}.start().
Inside the function restart(), it replaced the using of CountDownLatch with
serviceScope.launch {
withContext(Dispatchers.IO) {
doReset()
}
}
Question: this launch/withContext actually does not stop the code execution of the next if (!conDoProcess) -- so it fails to do what the latch used to do.
what is the right way to stop the code execution until the doReset() . is done?
Another question, when dispose this Processor object it calls serviceScope.cancel(),
what is the difference if call with serviceJob.cancel()?
class Processor {
private val serviceJob = Job()
private val serviceScope = CoroutineScope(Dispatchers.IO + serviceJob)
.........
/* return false if the it does not start the processing */
fun restart(): Boolean {
synchronized(_lock) {
.........
// 1.old code using latch to wait
/******************
val latch = CountDownLatch(1)
streamThreadPoolExecutor.execute {
doReset() //
latch.countDown()
}
latch.await(3, TimeUnit.SECONDS) // wait at most for 3 seconds if no one calls countDown
*******************/
// 2. change to using coroutines to suspend
serviceScope.launch {
withContext(Dispatchers.IO) {
doReset()
}
}
// wait until reset is done
if (!conDoProcess) {// the doRest() should update conDoProcess
return false
}
for (i in providers.indices) {
val pr = provider[i]
serviceScope.launch {
pr.doProcess()
}
}
return true
}
}
fun dispose() {
synchronized(_lock) {
.........
serviceScope.cancel()
// or should it use
// serviceJob.cancel()
//==========>
}
}
}
I think it used the serviceScope.launch wrong, it should include the rest part after the blocking part withContext(Dispatchers.IO), but inside the serviceScope.launch.
// 2. change to using coroutines to suspend
serviceScope.launch {
withContext(Dispatchers.IO) {
doReset()
}
// wait until reset is done
if (!conDoProcess) {// the doRest() should update conDoProcess
return false
}
for (i in providers.indices) {
val pr = provider[i]
serviceScope.launch {
pr.doProcess()
}
}
}
return true

How to continue a suspend function in a dynamic proxy in the same coroutine?

I want to continue a suspend function in a dynamic proxy in the same coroutine.
Please have a look at the following code:
interface Adder {
suspend fun add(a: Int, b: Int): Int
}
val IH = InvocationHandler { _, method, args ->
val continuation = args.last() as Continuation<*>
val realArgs = args.take(args.size - 1)
println("${method.name}$realArgs")
GlobalScope.launch {
delay(5_000)
#Suppress("UNCHECKED_CAST") (continuation as Continuation<Int>).resume(3)
}
COROUTINE_SUSPENDED
}
fun main() {
val adder = Proxy.newProxyInstance(
Adder::class.java.classLoader, arrayOf(Adder::class.java), IH
) as Adder
runBlocking {
println(adder.add(1, 2))
}
}
It works fine. It runs the delay function in a new coroutine.
However, that's not what I want.
I want to run the InvocationHandler in the same coroutine as the one that was started with runBlocking.
Something like:
val IH = InvocationHandler { _, _, _ ->
delay(5_000)
3
}
This obviously won't compile because delay is a suspend function that must be run in a coroutine.
So the question is: How could I write the InvocationHandler for my intended behavior?
Any help would be very much appreciated.
I'd like to use this code in my RPC framework.
My real code would replace the delay call with non-blocking Ktor socket calls for serializing the data over the wire.
You can find the code example at: https://raw.githubusercontent.com/softappeal/yass/master/kotlin/yass/test/ch/softappeal/yass/remote/SuspendProxy.kt
I've found a solution for my problem:
package ch.softappeal.yass
import kotlinx.coroutines.*
import java.lang.reflect.*
import kotlin.coroutines.*
import kotlin.test.*
typealias SuspendInvoker = suspend (method: Method, arguments: List<Any?>) -> Any?
private interface SuspendFunction {
suspend fun invoke(): Any?
}
private val SuspendRemover = SuspendFunction::class.java.methods[0]
#Suppress("UNCHECKED_CAST")
fun <C : Any> proxy(contract: Class<C>, invoker: SuspendInvoker): C =
Proxy.newProxyInstance(contract.classLoader, arrayOf(contract)) { _, method, arguments ->
val continuation = arguments.last() as Continuation<*>
val argumentsWithoutContinuation = arguments.take(arguments.size - 1)
SuspendRemover.invoke(object : SuspendFunction {
override suspend fun invoke() = invoker(method, argumentsWithoutContinuation)
}, continuation)
} as C
interface Adder {
suspend fun add(a: Int, b: Int): Int
}
class SuspendProxyTest {
#Test
fun test() {
val adder = proxy(Adder::class.java) { method, arguments ->
println("${method.name}$arguments")
delay(100)
3
}
runBlocking { assertEquals(3, adder.add(1, 2)) }
}
}
Any comments?
Is this a good/problematic solution?
Could/should the "removing of suspend functionality" be added to the kotlin.coroutines library?
use runBlocking inside InvocationHandler:
val IH = InvocationHandler { _, _, _ ->
runBlocking{
delay(5_000)// now you can use suspend functions here
}
3
}

How to build a monitor with infinite loop?

I am building a monitor in Kotlin to schedule certain operations, what I want is a program that inserts or updates some database entries for a given time intervall. What I got so far is a program that runs for a given time span, but I have an infinite loop in my porgram that takes up to 30% of processor power when it is not time for an update. So my question is how to build a monitor without an infinite loop?
this my code so far:
while(!operations.done && appConfigurations.run_with_monitor) {
if (DataSourceMonitor.isReadyForUpdate(lastMonitorModel)) {
operations.update()
}
}
operations is an entire sequence of different actions to execute. Each operation implementing the IScheduler interface.
interface IScheduler {
var done: Boolean
fun update()
fun reset()
}
Example of implementation:
class Repeat(private val task: IScheduler) : IScheduler {
override var done = false
override fun update() {
if (this.task.done) {
this.reset()
}
this.task.update()
//logger.info { "Update repeat, done is always $done" }
}
override fun reset() {
this.task.reset()
this.done = false
}
}
class Sequence(private val task1: IScheduler, private val task2: IScheduler): IScheduler {
override var done = false
var current = task1
var next = task2
override fun update() {
if (!this.done) {
this.current.update()
if (this.current.done) {
this.current = this.next
}
if (this.next.done) {
this.done = true
}
}
}
class Print(private val msg: String): IScheduler {
override var done = false
override fun update() {
println(this.msg)
this.done = true
}
override fun reset() {
this.done = false
}
}
The value of operations can be as follows:
val operations = Repeat(Sequence(Print("First action"), Print("Another action")))
**So right now my monitor is working and completely functional, but how can I improve the performance of the infinite loop? **
Hope anyone has some ideas about this.
If your DataSourceMonitor has no way to block until isReadyForUpdate is going to return true, then the usual approach is to add a delay. eg:
while(!operations.done && appConfigurations.run_with_monitor) {
if (DataSourceMonitor.isReadyForUpdate(lastMonitorModel)) {
operations.update()
} else {
Thread.sleep(POLL_DELAY);
}
}
If it's always ready for update there won't be any delay, but if it ever isn't ready for update then it'll sleep. You'll need to tune the POLL_DELAY. Bigger values mean less CPU usage, but greater latency in detecting new events to process. Smaller values produce less latency, but use more CPU.
If you really want to get fancy you can have the poll delay start small and then increase up to some maximum, dropping back down once events are found. This is probably overkill, but look up "adaptive polling" if you're interested.
I have refactored my code and I can accomplish the same result with less code, by removing the IScheduler interface by the abstract class TimerTask. The job can be done with these lines of code:
val scheduler = Sequence(
Print("Executed task 1"),
Sequence(Print("Executed task 2"),
Sequence(Print("Executed task 3"), Print("Finished Scheduler")))
)
Timer().schedule(scheduler, DELAY, PERIOD)
All the interface implementations are changed to TimerTask implementations:
class Print(private val msg: String): TimerTask() {
override fun run() {
println(msg)
}
}
class Sequence(private val task1: Runnable, private val task2: Runnable): TimerTask() {
override fun run() {
task1.run()
task2.run()
}
}

Resources