|
| 1 | +// This file should be a part of `CoroutineContext.common.kt`, but adding `JvmName` to that fails: KT-75248 |
| 2 | +@file:JvmName("CoroutineContextKt") |
| 3 | +@file:JvmMultifileClass |
| 4 | +package kotlinx.coroutines |
| 5 | + |
| 6 | +import kotlin.coroutines.ContinuationInterceptor |
| 7 | +import kotlin.coroutines.CoroutineContext |
| 8 | +import kotlin.coroutines.EmptyCoroutineContext |
| 9 | +import kotlin.jvm.JvmMultifileClass |
| 10 | +import kotlin.jvm.JvmName |
| 11 | + |
| 12 | +/** |
| 13 | + * Creates a context for a new coroutine. It installs [Dispatchers.Default] when no other dispatcher or |
| 14 | + * [ContinuationInterceptor] is specified and |
| 15 | + */ |
| 16 | +@ExperimentalCoroutinesApi |
| 17 | +public fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext { |
| 18 | + val combined = foldCopies(coroutineContext, context, true) |
| 19 | + val debug = wrapContextWithDebug(combined) |
| 20 | + return if (combined !== Dispatchers.Default && combined[ContinuationInterceptor] == null) |
| 21 | + debug + Dispatchers.Default else debug |
| 22 | +} |
| 23 | + |
| 24 | +/** |
| 25 | + * Creates a context for coroutine builder functions that do not launch a new coroutine, e.g. [withContext]. |
| 26 | + * @suppress |
| 27 | + */ |
| 28 | +@InternalCoroutinesApi |
| 29 | +public fun CoroutineContext.newCoroutineContext(addedContext: CoroutineContext): CoroutineContext { |
| 30 | + /* |
| 31 | + * Fast-path: we only have to copy/merge if 'addedContext' (which typically has one or two elements) |
| 32 | + * contains copyable elements. |
| 33 | + */ |
| 34 | + if (!addedContext.hasCopyableElements()) return this + addedContext |
| 35 | + return foldCopies(this, addedContext, false) |
| 36 | +} |
| 37 | + |
| 38 | +private fun CoroutineContext.hasCopyableElements(): Boolean = |
| 39 | + fold(false) { result, it -> result || it is CopyableThreadContextElement<*> } |
| 40 | + |
| 41 | +/** |
| 42 | + * Folds two contexts properly applying [CopyableThreadContextElement] rules when necessary. |
| 43 | + * The rules are the following: |
| 44 | + * - If neither context has CTCE, the sum of two contexts is returned |
| 45 | + * - Every CTCE from the left-hand side context that does not have a matching (by key) element from right-hand side context |
| 46 | + * is [copied][CopyableThreadContextElement.copyForChild] if [isNewCoroutine] is `true`. |
| 47 | + * - Every CTCE from the left-hand side context that has a matching element in the right-hand side context is [merged][CopyableThreadContextElement.mergeForChild] |
| 48 | + * - Every CTCE from the right-hand side context that hasn't been merged is copied |
| 49 | + * - Everything else is added to the resulting context as is. |
| 50 | + */ |
| 51 | +private fun foldCopies(originalContext: CoroutineContext, appendContext: CoroutineContext, isNewCoroutine: Boolean): CoroutineContext { |
| 52 | + // Do we have something to copy left-hand side? |
| 53 | + val hasElementsLeft = originalContext.hasCopyableElements() |
| 54 | + val hasElementsRight = appendContext.hasCopyableElements() |
| 55 | + |
| 56 | + // Nothing to fold, so just return the sum of contexts |
| 57 | + if (!hasElementsLeft && !hasElementsRight) { |
| 58 | + return originalContext + appendContext |
| 59 | + } |
| 60 | + |
| 61 | + var leftoverContext = appendContext |
| 62 | + val folded = originalContext.fold<CoroutineContext>(EmptyCoroutineContext) { result, element -> |
| 63 | + if (element !is CopyableThreadContextElement<*>) return@fold result + element |
| 64 | + // Will this element be overwritten? |
| 65 | + val newElement = leftoverContext[element.key] |
| 66 | + // No, just copy it |
| 67 | + if (newElement == null) { |
| 68 | + // For 'withContext'-like builders we do not copy as the element is not shared |
| 69 | + return@fold result + if (isNewCoroutine) element.copyForChild() else element |
| 70 | + } |
| 71 | + // Yes, then first remove the element from append context |
| 72 | + leftoverContext = leftoverContext.minusKey(element.key) |
| 73 | + // Return the sum |
| 74 | + @Suppress("UNCHECKED_CAST") |
| 75 | + return@fold result + (element as CopyableThreadContextElement<Any?>).mergeForChild(newElement) |
| 76 | + } |
| 77 | + |
| 78 | + if (hasElementsRight) { |
| 79 | + leftoverContext = leftoverContext.fold<CoroutineContext>(EmptyCoroutineContext) { result, element -> |
| 80 | + // We're appending new context element -- we have to copy it, otherwise it may be shared with others |
| 81 | + if (element is CopyableThreadContextElement<*>) { |
| 82 | + return@fold result + element.copyForChild() |
| 83 | + } |
| 84 | + return@fold result + element |
| 85 | + } |
| 86 | + } |
| 87 | + return folded + leftoverContext |
| 88 | +} |
0 commit comments