Appropriate use of coroutines for parallel API calls - kotlin

I'm trying to learn the right way to use Kotlin coroutines to do some parallel API calls. I've been able to get the expected results/process, but am wondering if there is a "more correct" way to do so.
Here's my contrived example of what I'm trying to do. Use case is essentially an inbound web request, and that request in turn needing to make a couple of API calls. Digging around led me to using launch or async:
// using launch
var recordExists = false
var otherRecordExists = false
coroutineScope {
launch {
recordExists = someHttpService.doesRecordExist(123)
}
launch {
otherRecordExists = someHttpService.doesRecordExist(456)
}
}
if (!recordExists || !otherRecordExists) {...}
vs
// using async
var recordExists = false
var otherRecordExists = false
coroutineScope {
val recordDeferred = async { someHttpService.doesRecordExist(123) }
val otherRecordDeferred = async { someHttpService.doesRecordExist(456) }
recordExists = recordDeferred.await()
otherRecordExists = otherRecordDeferred.await()
}
if (!recordExists || !otherRecordExists) {...}
I landed on using launch because it felt cleaner, but not sure if that was the most appropriate decision reasoning...
Is there a specific reason to use one over the other that I'm overlooking?

Your code is okay but as a general practice, I would recommend avoiding using var as much as possible when dealing with concurrency and parallelism. Having a shared mutable state is very risky and can often lead to race-conditions. In your case you can do something like:
coroutineScope {
val recordDeferred = async { someHttpService.doesRecordExist(123) }
val otherRecordDeferred = async { someHttpService.doesRecordExist(456) }
val recordExists = recordDeferred.await()
val otherRecordExists = otherRecordDeferred.await()
if (!recordExists || !otherRecordExists) {
...
}
}
Here you can only use async as you need to get some data back from the coroutine.

Related

Kotlin coroutines industry practices

I have written this function to load data from db, change it asynchronously and return the new data. It appears to work fine, but since I am new to koltin, I wanted to ask if this code is ok by industry standards.
override fun update(resourceCommand: UpdateResourceCommand): List<Resource> = runBlocking {
val resources = resourceCommand.resources.map {
async {
val resource = load(it)
resource.isProtected = it.isProtected
resource
}
}.awaitAll()
return#runBlocking resources
}
Thank you in advance
The question is kind of subjective, but my take on it is that is not really idiomatic to create a variable to only return it after. I believe your code can be made more compact like
override fun update(resourceCommand: UpdateResourceCommand): List<Resource> = runBlocking {
resourceCommand.resources.map {
async {
load(it).apply {
isProtected = it.isProtected
}
}
}.awaitAll()
}
Whether it's better or more to industry standards I don't know and is subjective. But that's how I would do it.

How to efficiently perform concurrent computation with coroutines

I'm trying to improve my knowledge of coroutines and currently working on following problem:
Given a random non empty string with a length of 14 characters, what would be the most efficient way to find a string that contains a specific prefix (let's assume prefix length is 5)?
Most of the solutions I encountered on the internet either a) manually launch async{} 2 or 3 times or b) launch async{} in a loop and then await all of them to complete which won't work for this scenario.
One approach I tried was to launch new coroutines until I get a non null repsonse from the computation function and cancel the scope after, however there's a clear a performance issue that I'm not seeing since this approach can take more than 20s to calculate for a prefix with length 1.
...
private val _flow = MutableSharedFlow<String>()
suspend fun invoke(prefix: String) = withContext(dispatcher) { // dispatcher is Dispatchers.Default
_flow.onEach {
println("String is=$it")
this.cancel()
}.launchIn(this)
repeat(Int.MAX_VALUE) {
launch {
getString(prefix)?.let {
_flow.emit(it)
}
}
}
}
private fun getString(prefix: String): String? { // or any other cpu intensive task
val randomString = generateRandomStringAccordingToSpecs() // implemented elsewhere
if (randomString .startsWith(prefix = "prefix", ignoreCase = true)) {
return randomString
} else {
return null
}
}
I also tried an approach with a while loop and 4 parallel executions, for which I'm getting better performace results, however awaiting after every X calculations doesn't seem like the most efficient solution to me:
suspend fun invoke(prefix: String) = withContext(dispatcher) {
var resultString: String? = getString(prefix)
while (resultString == null) {
val tasks = listOf(
async { getString(prefix) },
async { getString(prefix) },
async { getString(prefix) },
async { getString(prefix) }
)
resultString = tasks.awaitAll().filterNotNull().firstOrNull()
}
println("String is=$resultString")
}
private fun getString(prefix: String): String? { // or any other cpu intensive task
val randomString = generateRandomStringAccordingToSpecs() // implemented elsewhere
if (randomString .startsWith(prefix = "prefix", ignoreCase = true)) {
return randomString
} else {
return null
}
}
In the example above I'm using a find suffix problem, but in general, what is the most efficient way to concurrently perform some CPU intensive calculations with coroutines?
Especially for the calculations where we don't know how many times the task must be executed before we get an answer.
This seems like a job for the select function. Assuming your generateRandomStringAccordingToSpecs() is a computationally blocking function, you want to have all your CPU cores working on the problem simultaneously and you just want the first valid result, you could build an operator like this:
suspend fun <T> getFirstResult(block: suspend CoroutineScope.() -> T): T =
withContext(Dispatchers.Default) {
coroutineScope {
select {
repeat(Runtime.getRuntime().availableProcessors()) {
async { block() }.onAwait {
coroutineContext.cancelChildren()
it
}
}
}
}
}
It starts as many parallel coroutines as there are CPUs, and once any of them returns a result, it cancels the rest and returns that result.
So you can use this with a coroutine block that uses a while loop indefinitely until a result is returned:
suspend fun invoke(prefix: String) = getFirstResult {
while(isActive) {
return#getFirstResult getString(prefix) ?: continue
}
}

Why compose ui testing's IdlingResource is blocking the main thread?

I've written a "minimal" AS project to replicate my the problem I'm facing. Here's the gh link.
I'm trying to write an end-to-end ui test in my compose-only project. The test covers a simple sign-in -> sync data -> go to main view use case.
Here's the whole test:
#HiltAndroidTest
class ExampleInstrumentedTest {
#get:Rule(order = 1)
val hiltRule = HiltAndroidRule(this)
#get:Rule(order = 2)
val composeTestRule = createAndroidComposeRule<MainActivity>()
#Inject
lateinit var dao: DummyDao
val isSyncing = mutableStateOf(false)
#Before
fun setup() {
runBlocking {
hiltRule.inject()
dao.deleteAllData()
dao.deleteUser()
}
composeTestRule.activity.isSyncingCallback = {
synchronized(isSyncing) {
isSyncing.value = it
}
}
composeTestRule.registerIdlingResource(
object : IdlingResource {
override val isIdleNow: Boolean
get() {
synchronized(isSyncing) {
return !isSyncing.value
}
}
}
)
}
#Test
fun runsTheStuffAndItWorks() {
composeTestRule
.onNodeWithText("login", ignoreCase = true, useUnmergedTree = true)
.assertIsDisplayed()
.performClick()
composeTestRule
.onNodeWithTag("sync")
.assertExists()
composeTestRule.waitForIdle()
assertFalse(isSyncing.value)
composeTestRule.onRoot().printToLog("not in the list")
composeTestRule
.onNodeWithTag("the list", useUnmergedTree = true)
.assertIsDisplayed()
}
}
The test runs "alright" up to the point where it should be waiting for the sync worker to finish its job and finally navigate to the "main composable".
Unfortunately, the test seems to be blocking the device's ui thread when the idling resource is not idle, finishing the test immediately as the idling resource does become idle.
I've tried using Espresso's IdlingResource directly, which also didn't work, showing similar results. I've tried adding compose's IdlingResource in different points as well, but that also didn't work (adding one between navigation calls also blocks the UI thread and the test fails even sooner).
What am I doing wrong here? Am I forgetting to setup something?

Kotlin Coroutine/Flow Timeout without cancelling the running coroutine?

I am trying to create a Flow that emits a value after a timeout, without cancelling the underlying coroutine. The idea is that the network call has X time to complete and emit a value and after that timeout has been reached, emit some initial value without cancelling the underlying work (eventually emitting the value from the network call, assuming it succeeds).
Something like this seems like it might work, but it would cancel the underlying coroutine when the timeout is reached. It also doesn't handle emitting some default value on timeout.
val someFlow = MutableStateFlow("someInitialValue")
val deferred = async {
val networkCallValue = someNetworkCall()
someFlow.emit(networkCallValue)
}
withTimeout(SOME_NUMBER_MILLIS) {
deferred.await()
}
I'd like to be able to emit the value returned by the network call at any point, and if the timeout is reached just emit some default value. How would I accomplish this with Flow/Coroutines?
One way to do this is with a simple select clause:
import kotlinx.coroutines.selects.*
val someFlow = MutableStateFlow("someInitialValue")
val deferred = async {
someFlow.value = someNetworkCall()
}
// await the first of the 2 things, without cancelling anything
select<Unit> {
deferred.onAwait {}
onTimeout(SOME_NUMBER_MILLIS) {
someFlow.value = someDefaultValue
}
}
You would have to watch out for race conditions though, if this runs on a multi-threaded dispatcher. If the async finished just after the timeout, there is a chance the default value overwrites the network response.
One way to prevent that, if you know the network can't return the same value as the initial value (and if no other coroutine is changing the state) is with the atomic update method:
val deferred = async {
val networkCallValue = someNetworkCall()
someFlow.update { networkCallValue }
}
// await the first of the 2 things, without cancelling anything
val initialValue = someFlow.value
select<Unit> {
deferred.onAwait {}
onTimeout(300) {
someFlow.update { current ->
if (current == initialValue) {
"someDefaultValue"
} else {
current // don't overwrite the network result
}
}
}
}
If you can't rely on comparisons of the state, you can protect access to the flow with a Mutex and a boolean:
val someFlow = MutableStateFlow("someInitialValue")
val mutex = Mutex()
var networkCallDone = false
val deferred = async {
val networkCallValue = someNetworkCall()
mutex.withLock {
someFlow.value = networkCallValue
networkCallDone = true
}
}
// await the first of the 2 things, without cancelling anything
select<Unit> {
deferred.onAwait {}
onTimeout(300) {
mutex.withLock {
if (!networkCallDone) {
someFlow.value = "someDefaultValue"
}
}
}
}
Probably the easiest way to solve the race condition is to use select() as in #Joffrey's answer. select() guarantees to execute only a single branch.
However, I believe mutating a shared flow concurrently complicates the situation and introduces another race condition that we need to solve. Instead, we can do it really very easily:
flow {
val network = async { someNetworkCall() }
select {
network.onAwait{ emit(it) }
onTimeout(1000) {
emit("initial")
emit(network.await())
}
}
}
There are no race conditions to handle. We have just two simple execution branches, depending on what happened first.
If we need a StateFlow then we can use stateIn() to convert a regular flow. Or we can use a MutableStateFlow as in the question, but mutate it only inside select(), similarly to above:
select {
network.onAwait{ someFlow.value = it }
onTimeout(1000) {
someFlow.value = "initial"
someFlow.value = network.await()
}
}
You can launch two coroutines simultaneously and cancel the Job of the first one, which responsible for emitting default value, in the second one:
val someFlow = MutableStateFlow("someInitialValue")
val firstJob = launch {
delay(SOME_NUMBER_MILLIS)
ensureActive() // Ensures that current Job is active.
someFlow.update {"DefaultValue"}
}
launch {
val networkCallValue = someNetworkCall()
firstJob.cancelAndJoin()
someFlow.update { networkCallValue }
}
You can send the network request and start the timeout delay simultaneously. When the network call succeeds, update the StateFlow with the response. And, when the timeout finishes and we haven't received the response, update the StateFlow with the default value.
val someFlow = MutableStateFlow(initialValue)
suspend fun getData() {
launch {
someFlow.value = someNetworkCall()
}
delay(TIMEOUT_MILLIS)
if(someFlow.value == initialValue)
someFlow.value = defaultValue
}
If the response of the network call can be same as the initialValue, you can create a new Boolean to check the completion of network request. Another option can be to store a reference of the Job returned by launch and check if job.isActive after the timeout.
Edit: In case you want to cancel delay when the network request completes, you can do something like:
val someFlow = MutableStateFlow(initialValue)
suspend fun getData() {
val job = launch {
delay(TIMEOUT_MILLIS)
someFlow.value = defaultValue
}
someFlow.value = someNetworkCall()
job.cancel()
}
And to solve the possible concurrency issue, you can use MutableStateFlow.update for atomic updates.

Modifying ktors call co-routine context

So I'm using ktor and want to have some data available throughout the context, via a ThreadLocal. What I am looking at is:
val dataThreadLocal = ThreadLocal<String>()
suspend fun fromOtherFunction() = "From other function -- ${dataThreadLocal.get()}"
routing {
get("/hello") {
// [1]
launch(this.coroutineContext + dataThreadLocal.asContextElement(value = "world")) {
val fromHere = async {
"ThreadLocal value: ${dataThreadLocal.get()}"
}
val fromThere = async {
fromOtherFunction()
}
call.respond(fromHere.await() + "," + fromThere.await())
}
}
}
What I am doing is that making sure all and any functions called from the parent launch (labelled [1]) have access to this kind of "scoped" data.
However, my application is quite big and I want this to work for all requests handled by routing and not want to have every route require this wrapping.
What would be really great is:
intercept(ApplicationCallPipeline.Features) {
launch(this.coroutineContext + dataThreadLocal.asContextElement(value = "world")) {
...
}
}
routing {
get("/hello") {
val threadLocalValue = dataThreadLocal.get()
...
Which obviously does not work, becase the coroutine scope in the intercept does not enclose the route's coroutine scope.
What I believe happens under the hood is that every call is launched in a parent co-routine scope (similar to how we have 'request threads'). Is there a way for me to modify the context in that? Is there a way where I can tell ApplicationEngine to + an additional context element whenever this new co-routine context is started?