blob: 8de7b2a8d1cbdb899b35744bd486761f8e298e7c [file] [log] [blame]
package kotlinx.coroutines.experimental
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater
import kotlin.coroutines.experimental.CoroutineContext
import kotlin.coroutines.experimental.startCoroutine
/**
* Lazy deferred value is conceptually a non-blocking cancellable future that is started on
* the first [await] or [start] invocation.
* It is created with [lazyDefer] coroutine builder.
*
* Unlike a simple [Deferred] value, a lazy deferred value has three states:
* * _Pending_ -- before the starts of the coroutine ([isActive] is `true`, but [isComputing] is `false`).
* * _Computing_ -- while computing the value ([isActive] is `true` and [isComputing] is `true`).
* * _Complete_ -- when done computing the value ([isActive] is `false` and [isComputing] is `false`).
*
* If this lazy deferred value is [cancelled][cancel], then it becomes immediately complete and
* cancels ongoing computation coroutine if it was started.
*/
public interface LazyDeferred<out T> : Deferred<T> {
/**
* Returns `true` if the coroutine is computing its value.
*/
public val isComputing: Boolean
/**
* Starts coroutine to compute this lazily deferred value. The result `true` if this invocation actually
* started coroutine or `false` if it was already started or cancelled.
*/
public fun start(): Boolean
}
/**
* Lazily starts new coroutine on the first [await][Deferred.await] or [start][LazyDeferred.start] invocation
* on the resulting [LazyDeferred].
* The running coroutine is cancelled when the resulting value is [cancelled][Job.cancel].
*
* The [context] for the new coroutine must be explicitly specified.
* See [CoroutineDispatcher] for the standard [context] implementations that are provided by `kotlinx.coroutines`.
* The [context][CoroutineScope.context] of the parent coroutine from its [scope][CoroutineScope] may be used,
* in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine.
*/
public fun <T> lazyDefer(context: CoroutineContext, block: suspend CoroutineScope.() -> T) : LazyDeferred<T> =
LazyDeferredCoroutine(newCoroutineContext(context), block).apply {
initParentJob(context[Job])
}
private class LazyDeferredCoroutine<T>(
context: CoroutineContext,
val block: suspend CoroutineScope.() -> T
) : DeferredCoroutine<T>(context), LazyDeferred<T> {
@Volatile
var lazyState: Int = STATE_PENDING
companion object {
private val STATE_PENDING = 0
private val STATE_COMPUTING = 1
private val STATE_COMPLETE = 2
private val LAZY_STATE: AtomicIntegerFieldUpdater<LazyDeferredCoroutine<*>> =
AtomicIntegerFieldUpdater.newUpdater(LazyDeferredCoroutine::class.java, "lazyState")
}
/*
=== State linking & linearization of the overall state ===
There are two state variables in this object and they have to update atomically from the standpoint of
external observer:
1. Job.state that is used by isActive function.
2. lazyState that is used to make sure coroutine starts at most once.
External observer must see only three states, not four, i.e. it should not be able
to see `isActive == false`, but `isComputing == true`.
On completion/cancellation state variables are updated in this order:
a) state <- complete (isComplete starts returning true)
b) lazyState <- STATE_COMPLETE (see onStateUpdate)
This is why, `isComputing` checks state variables in reverse order:
a) lazyState is checked _first_
b) isActive is checked after it
This way cancellation/completion is atomic w.r.t to all state functions.
`start` function also has to check lazyState _before_ isActive.
*/
override val isComputing: Boolean get() = lazyState == STATE_COMPUTING && isActive
override fun start(): Boolean {
while (true) { // lock-free loop on lazyState
when (lazyState) { // volatile read
STATE_PENDING -> {
if (isActive) { // then volatile read Job.state (inside isActive)
// can try to start
if (LAZY_STATE.compareAndSet(this, STATE_PENDING, STATE_COMPUTING)) {
block.startCoroutine(this, this)
return true
}
} else {
// cannot start -- already complete -- help update lazyState
lazyState = STATE_COMPLETE
return false
}
}
else -> return false
}
}
}
override fun onStateUpdate(update: Any?) {
lazyState = STATE_COMPLETE
}
}