| /* |
| * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. |
| */ |
| |
| package kotlinx.coroutines |
| |
| import kotlinx.coroutines.internal.* |
| import kotlinx.coroutines.scheduling.* |
| import kotlin.coroutines.* |
| import kotlin.coroutines.jvm.internal.CoroutineStackFrame |
| |
| internal const val COROUTINES_SCHEDULER_PROPERTY_NAME = "kotlinx.coroutines.scheduler" |
| |
| internal val useCoroutinesScheduler = systemProp(COROUTINES_SCHEDULER_PROPERTY_NAME).let { value -> |
| when (value) { |
| null, "", "on" -> true |
| "off" -> false |
| else -> error("System property '$COROUTINES_SCHEDULER_PROPERTY_NAME' has unrecognized value '$value'") |
| } |
| } |
| |
| internal actual fun createDefaultDispatcher(): CoroutineDispatcher = |
| if (useCoroutinesScheduler) DefaultScheduler else CommonPool |
| |
| /** |
| * Creates context for the new coroutine. It installs [Dispatchers.Default] when no other dispatcher nor |
| * [ContinuationInterceptor] is specified, and adds optional support for debugging facilities (when turned on). |
| * |
| * See [DEBUG_PROPERTY_NAME] for description of debugging facilities on JVM. |
| */ |
| @ExperimentalCoroutinesApi |
| public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext { |
| val combined = coroutineContext + context |
| val debug = if (DEBUG) combined + CoroutineId(COROUTINE_ID.incrementAndGet()) else combined |
| return if (combined !== Dispatchers.Default && combined[ContinuationInterceptor] == null) |
| debug + Dispatchers.Default else debug |
| } |
| |
| /** |
| * Executes a block using a given coroutine context. |
| */ |
| internal actual inline fun <T> withCoroutineContext(context: CoroutineContext, countOrElement: Any?, block: () -> T): T { |
| val oldValue = updateThreadContext(context, countOrElement) |
| try { |
| return block() |
| } finally { |
| restoreThreadContext(context, oldValue) |
| } |
| } |
| |
| /** |
| * Executes a block using a context of a given continuation. |
| */ |
| internal actual inline fun <T> withContinuationContext(continuation: Continuation<*>, countOrElement: Any?, block: () -> T): T { |
| val context = continuation.context |
| val oldValue = updateThreadContext(context, countOrElement) |
| val undispatchedCompletion = if (oldValue !== NO_THREAD_ELEMENTS) { |
| // Only if some values were replaced we'll go to the slow path of figuring out where/how to restore them |
| continuation.updateUndispatchedCompletion(context, oldValue) |
| } else { |
| null // fast path -- don't even try to find undispatchedCompletion as there's nothing to restore in the context |
| } |
| try { |
| return block() |
| } finally { |
| if (undispatchedCompletion == null || undispatchedCompletion.clearThreadContext()) { |
| restoreThreadContext(context, oldValue) |
| } |
| } |
| } |
| |
| internal fun Continuation<*>.updateUndispatchedCompletion(context: CoroutineContext, oldValue: Any?): UndispatchedCoroutine<*>? { |
| if (this !is CoroutineStackFrame) return null |
| /* |
| * Fast-path to detect whether we have unispatched coroutine at all in our stack. |
| * |
| * Implementation note. |
| * If we ever find that stackwalking for thread-locals is way too slow, here is another idea: |
| * 1) Store undispatched coroutine right in the `UndispatchedMarker` instance |
| * 2) To avoid issues with cross-dispatch boundary, remove `UndispatchedMarker` |
| * from the context when creating dispatched coroutine in `withContext`. |
| * Another option is to "unmark it" instead of removing to save an allocation. |
| * Both options should work, but it requires more careful studying of the performance |
| * and, mostly, maintainability impact. |
| */ |
| val potentiallyHasUndispatchedCorotuine = context[UndispatchedMarker] !== null |
| if (!potentiallyHasUndispatchedCorotuine) return null |
| val completion = undispatchedCompletion() |
| completion?.saveThreadContext(context, oldValue) |
| return completion |
| } |
| |
| internal tailrec fun CoroutineStackFrame.undispatchedCompletion(): UndispatchedCoroutine<*>? { |
| // Find direct completion of this continuation |
| val completion: CoroutineStackFrame = when (this) { |
| is DispatchedCoroutine<*> -> return null |
| else -> callerFrame ?: return null // something else -- not supported |
| } |
| if (completion is UndispatchedCoroutine<*>) return completion // found UndispatchedCoroutine! |
| return completion.undispatchedCompletion() // walk up the call stack with tail call |
| } |
| |
| /** |
| * Marker indicating that [UndispatchedCoroutine] exists somewhere up in the stack. |
| * Used as a performance optimization to avoid stack walking where it is not nesessary. |
| */ |
| private object UndispatchedMarker: CoroutineContext.Element, CoroutineContext.Key<UndispatchedMarker> { |
| override val key: CoroutineContext.Key<*> |
| get() = this |
| } |
| |
| // Used by withContext when context changes, but dispatcher stays the same |
| internal actual class UndispatchedCoroutine<in T>actual constructor ( |
| context: CoroutineContext, |
| uCont: Continuation<T> |
| ) : ScopeCoroutine<T>(if (context[UndispatchedMarker] == null) context + UndispatchedMarker else context, uCont) { |
| |
| private var savedContext: CoroutineContext? = null |
| private var savedOldValue: Any? = null |
| |
| fun saveThreadContext(context: CoroutineContext, oldValue: Any?) { |
| savedContext = context |
| savedOldValue = oldValue |
| } |
| |
| fun clearThreadContext(): Boolean { |
| if (savedContext == null) return false |
| savedContext = null |
| savedOldValue = null |
| return true |
| } |
| |
| override fun afterResume(state: Any?) { |
| savedContext?.let { context -> |
| restoreThreadContext(context, savedOldValue) |
| savedContext = null |
| savedOldValue = null |
| } |
| // resume undispatched -- update context but stay on the same dispatcher |
| val result = recoverResult(state, uCont) |
| withContinuationContext(uCont, null) { |
| uCont.resumeWith(result) |
| } |
| } |
| } |
| |
| internal actual val CoroutineContext.coroutineName: String? get() { |
| if (!DEBUG) return null |
| val coroutineId = this[CoroutineId] ?: return null |
| val coroutineName = this[CoroutineName]?.name ?: "coroutine" |
| return "$coroutineName#${coroutineId.id}" |
| } |
| |
| private const val DEBUG_THREAD_NAME_SEPARATOR = " @" |
| |
| internal data class CoroutineId( |
| val id: Long |
| ) : ThreadContextElement<String>, AbstractCoroutineContextElement(CoroutineId) { |
| companion object Key : CoroutineContext.Key<CoroutineId> |
| override fun toString(): String = "CoroutineId($id)" |
| |
| override fun updateThreadContext(context: CoroutineContext): String { |
| val coroutineName = context[CoroutineName]?.name ?: "coroutine" |
| val currentThread = Thread.currentThread() |
| val oldName = currentThread.name |
| var lastIndex = oldName.lastIndexOf(DEBUG_THREAD_NAME_SEPARATOR) |
| if (lastIndex < 0) lastIndex = oldName.length |
| currentThread.name = buildString(lastIndex + coroutineName.length + 10) { |
| append(oldName.substring(0, lastIndex)) |
| append(DEBUG_THREAD_NAME_SEPARATOR) |
| append(coroutineName) |
| append('#') |
| append(id) |
| } |
| return oldName |
| } |
| |
| override fun restoreThreadContext(context: CoroutineContext, oldState: String) { |
| Thread.currentThread().name = oldState |
| } |
| } |