'Shared lazy coroutine / memoized coroutine execution

Context:

import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope

data class DatabaseData(
    val a: String,
    val b: String
)

interface DatabaseFetcher {
    suspend fun get(): DatabaseData
}

class MyClass(
    private val databaseFetcher: DatabaseFetcher
) {
    suspend fun a() = coroutineScope {
        val a = async { databaseFetcher.get().a }
        //imagine more async{}'s here
        a //imagine a gets computed in regards to the other async{}'s as well
    }

    suspend fun b() = databaseFetcher.get().b
}

class MyController(private val databaseFetcher: DatabaseFetcher) {
    suspend fun someFun() = coroutineScope {
        // the reduced example doesn't need a coroutineScope of course, imagine some async{} here
        MyClass(databaseFetcher)
    }
}

I am trying to call databaseFetcher.get() only once if either a() or b() are called on MyClass. Basically a lazy future that gets started when either a() or b() is called, but with coroutines.

What I have tried so far:

  • Can't use by lazy{} as the coroutineScope matters here and I can't use withContext(Dispatchers.IO) as I use a custom Context (multithreading, Spring request scoped data etc.) - passing my context in here seems awkward (would it be bad practice?)
  • I can't pass an async(start = CoroutineStart.LAZY) when constructing MyClass as it would block indefinitely if the Deferred<T> is never await()ed on, which may happen when neither a() or b() is called. It also blocks indefinitely because the corresponding coroutineScope is constructed when MyClass is constructed which would block as a() and b() are called later after MyClass has been fully constructed because (as I understand) a coroutineScope is only unblocked when all its children are done, which doesn't hold true for a lazy async thats awaited outside the curent scope
  • Using a wider coroutine context may leak when the lazy async is never awaited - is this true? I couldn't find much about this

This is being done in the context of GraphQL wherein either a b or both can be selected. There are boilerplatey solutions to this but as I am still learning about coroutines I wondered if there is an elegant solution to this which I don't see yet. The CoroutineStart.LAZY issue really caught me by surprise :)



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source