How to find first desired result from kotlin coroutines Deferred<> (server)

Viewed 220

I’ve built a sharding library and i’m trying to add coroutine functionality to it. In the following snippet it returns the first true result that it finds:

override fun emailExists(email: String): Boolean {
    return runBlocking {
        shards
            .asyncAll { userDao.emailExists(email) }
            .map { it.await() }
            .firstOrNull { it }
    } ?: false
}

the shards.asyncAll method is:

fun <T> async(
    shardId: Long,
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T): Deferred<T> {
    return scope.async(context, start) {
        selectShard(shardId)
        block()
    }
}
fun <T> asyncAll(
    shardIds: Collection<Long> = this.shardIds,
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T): List<Deferred<T>> {
    return shardIds.map { async(it, context, start, block) }
}

This works, but it consults the shards in order for their return, meaning if the first shard takes a very long time to return and it doesn't return true but the second shard returns immediately with a value of true we're still waiting as long as the first shard took to return. Is there a better way to wait on values for a collection of Deferred<>'s and process them in the order that they return so that I can exit as early as possible?

1 Answers

Even if you were to get your answer early, runBlocking would still wait for all the coroutines you started to complete before returning.

In order to run the kind of coroutine race you're looking for:

  1. When the first task completes with true, it needs store that result and cancel the parent job of all the other tasks; and
  2. The other tasks should properly abort when cancelled.

Unfortunately, I'm pretty sure Kotlin doesn't include a function that does this, so you have to do it yourself. The easiest way is probably to have each throw an exception that indicates a true result. You can then use awaitAll on the group, catch the exception, and extract the result.

Related