https://kotlinlang.org logo
#getting-started
Title
# getting-started
l

Lawrence

06/10/2022, 6:35 PM
I have got two APIs A and B such that only one maybe active at a time but not both. I wanted to determine which type I am connected to by making two parallel calls to their respective APIs and select the first successful one. I tried using select expression with a timeout of 15 seconds. Is this a good way to achieve such a thing?
Copy code
// The api I am working with is blocking and throws an error if not connected 
        suspend fun determineType(): Type?  {
            var type: Type? = null
            withTimeoutOrNull(15.seconds) {
                while (type == null) {
                    select<Unit> {
                        async {
                            runInterruptible(<http://Dispatchers.IO|Dispatchers.IO>) {
                                kotlin.runCatching { apiA.typeA }.getOrNull()
                            }
                        }.onAwait {
                            if (it != null) {
                                make = it
                            }
                        }
                        async {
                            runInterruptible(<http://Dispatchers.IO|Dispatchers.IO>) {
                                kotlin.runCatching { apiB.typeB }.getOrNull()
                            }
                        }.onAwait {
                            if (it != null) {
                                type = it
                            }
                        }
                    }
                }
            }
            return type
        }
m

mkrussel

06/10/2022, 6:42 PM
I would think you should cal
async
before the select and the while, and then just do the
onAwaits
inside the select. This is for flows, but it should give you a pattern to follow. https://proandroiddev.com/implement-race-amb-operator-with-kotlin-coroutines-flow-a59f17997b67
p

phldavies

06/10/2022, 7:22 PM
Arrow has a raceN operator that would satisfy your usecase (and cancel the loser)
l

Lawrence

06/10/2022, 8:18 PM
I think I came up with this at the end
Copy code
@OptIn(ExperimentalCoroutinesApi::class)
    suspend fun determine() = coroutineScope {
      var type: Type? = null
      val jobs =
          listOf(apiA, apiB).map {
            async(<http://Dispatchers.IO|Dispatchers.IO>) {
              runInterruptible {
                kotlin.runCatching { it.callApi() }.getOrNull()
              }
            }
          }
      whileSelect {
        onTimeout(15.seconds) { false }
        jobs.forEach { deferred ->
          deferred.onAwait {
            it?.let {
              type = it
              false
            }
                ?: true
          }
        }
      }
          .also { coroutineContext.cancelChildren() }
      type
    }
  }
e

ephemient

06/11/2022, 7:06 AM
if they both fail, that code will wait 15 seconds regardless
I think it would be easier to use something like
Copy code
suspend fun Iterable<() -> Unit>.indexOfSuccess() = mapIndexed { i, block ->
    flow {
        runInterruptible {
            runCatching(block)
        }.onSuccess { emit(i) }
    }
}.merge().firstOrNull()

withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
    withTimeoutOrNull(15_000) {
        listOf(apiA::callApi, apiB::callApi).indexOfSuccess()
    }
}
r

rcgroot

06/14/2022, 1:09 PM
Faced something similar in the past. Every bit of coroutine fuu I had did not work because the
it.callApi()
part in my case was not-interruptible. I think I had to resort to something (horrible) as running the
callApi()
in a different scope, because cancel will detach a scope immediately.
4 Views