blob: b2a6cc72e234c5fde9db6ed58b3c03ed164c45d0 [file] [log] [blame]
/*
* Copyright 2016-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kotlinx.coroutines.experimental
import kotlin.coroutines.experimental.Continuation
import kotlin.coroutines.experimental.intrinsics.COROUTINE_SUSPENDED
private const val UNDECIDED = 0
private const val SUSPENDED = 1
private const val RESUMED = 2
/**
* @suppress **This is unstable API and it is subject to change.**
*/
internal abstract class AbstractContinuation<in T>(
public final override val delegate: Continuation<T>,
public final override val resumeMode: Int
) : JobSupport(true), Continuation<T>, DispatchedTask<T> {
private var decision = UNDECIDED
/* decision state machine
+-----------+ trySuspend +-----------+
| UNDECIDED | -------------> | SUSPENDED |
+-----------+ +-----------+
|
| tryResume
V
+-----------+
| RESUMED |
+-----------+
Note: both tryResume and trySuspend can be invoked at most once, first invocation wins
*/
override fun takeState(): Any? = state
private fun trySuspend(): Boolean = when (decision) {
UNDECIDED -> { decision = SUSPENDED; true }
RESUMED -> false
else -> error("Already suspended")
}
private fun tryResume(): Boolean = when (decision) {
UNDECIDED -> { decision = RESUMED; true }
SUSPENDED -> false
else -> error("Already resumed")
}
@PublishedApi
internal fun getResult(): Any? {
if (trySuspend()) return COROUTINE_SUSPENDED
// otherwise, afterCompletion was already invoked & invoked tryResume, and the result is in the state
val state = this.state
if (state is CompletedExceptionally) throw state.exception
return getSuccessfulResult(state)
}
override fun afterCompletion(state: Any?, mode: Int) {
if (tryResume()) return // completed before getResult invocation -- bail out
// otherwise, getResult has already commenced, i.e. completed later or in other thread
dispatch(mode)
}
override fun resume(value: T) =
resumeImpl(value, resumeMode)
override fun resumeWithException(exception: Throwable) =
resumeImpl(CompletedExceptionally(exception), resumeMode)
protected fun resumeImpl(proposedUpdate: Any?, resumeMode: Int) {
val state = this.state
return when (state) {
is Incomplete -> updateState(proposedUpdate, resumeMode)
is Cancelled -> {
// Ignore resumes in cancelled coroutines, but handle exception if a different one here
if (proposedUpdate is CompletedExceptionally && proposedUpdate.exception != state.exception)
handleException(proposedUpdate.exception)
return
}
else -> error("Already resumed, but got $proposedUpdate")
}
}
override fun handleException(exception: Throwable) {
handleCoroutineException(context, exception)
}
}