Martin Devillers
09/27/2018, 8:19 AMDeferredCache
with an await
method which when called:
- if no coroutine to load the resource has been launched, it starts a new one
- if a coroutine to load the resource has finished successfully, it returns its result
- if a coroutine to load the the resource has finished with an error, it restarts a new one
- if a coroutine to load the resource has been launched and is still active, it awaits its result
My current implementation looks like this
class DeferredCache<T: Any>(
private val coroutineScope: CoroutineScope = GlobalScope,
private val block: suspend CoroutineScope.() -> T
) {
private var deferredValue: Deferred<T>? = null
private val deferredValueLock = Mutex()
private val usableDeferredValue: Deferred<T>?
get() = deferredValue?.takeUnless { it.isCompletedExceptionally }
suspend fun await(): T {
val deferredValue = usableDeferredValue ?: deferredValueLock.withLock {
usableDeferredValue ?: coroutineScope.async(NonCancellable, block = block).also { deferredValue = it }
}
return deferredValue.await()
}
}
Can you think of a more optimal solution? Anyone running into this scenario as well? Might it be worth adding a “standard” way to do this?spand
09/27/2018, 8:28 AMcbruegg
09/27/2018, 8:30 AMdeferredValue
be @Volatile
?Martin Devillers
09/27/2018, 8:30 AMcoroutineStart
parameter in coroutine builder functions), but it’s rather the “auto-restart” aspect which is missing from the framework.cbruegg
09/27/2018, 8:31 AMdeferredValue
may be accessed from any thread and is not accessed using JVM monitor synchronization. So what one thread writes, another doesn't necessarily have to see, from my understanding.spand
09/27/2018, 8:34 AMnull
in deferredValue
it proceeds to take the lock in which case it should get the updated value (ie. I dont see a problem)cbruegg
09/27/2018, 8:36 AMdeferredValue
to its own cache, not to main memory, which means that other threads wouldn't see the change until the cache is flushed.block
can be executed more than once.spand
09/27/2018, 8:41 AMwithLock
establishes the happens before relation. It javadoc says:
JVM API note:
* Memory semantic of the [Mutex] is similar to `synchronized` block on JVM:
* An unlock on a [Mutex] happens-before every subsequent successful lock on that [Mutex].
* Unsuccessful call to [tryLock] do not have any memory effects.
cbruegg
09/27/2018, 8:47 AMspand
09/27/2018, 8:47 AMMartin Devillers
09/27/2018, 8:48 AMspand
09/27/2018, 8:53 AMMartin Devillers
09/27/2018, 8:55 AMspand
09/27/2018, 8:55 AMelizarov
09/27/2018, 9:33 AMMartin Devillers
09/27/2018, 9:39 AMawait
is called again.elizarov
09/27/2018, 9:46 AM