From fe346230b2c313ec19c370765d498b62af5c8e62 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Mon, 31 Jan 2022 16:01:59 +0300 Subject: [PATCH 01/26] Prototype of merge and eager copy --- .../jvm/src/CoroutineContext.kt | 63 +++++++--- .../jvm/src/ThreadContextElement.kt | 19 ++- .../jvm/test/ThreadContextElementTest.kt | 8 +- .../test/ThreadContextMutableCopiesTest.kt | 113 ++++++++++++++++++ 4 files changed, 182 insertions(+), 21 deletions(-) create mode 100644 kotlinx-coroutines-core/jvm/test/ThreadContextMutableCopiesTest.kt diff --git a/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt b/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt index d562207f8b..9ee2b1983b 100644 --- a/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt +++ b/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt @@ -10,35 +10,68 @@ import kotlin.coroutines.jvm.internal.CoroutineStackFrame /** * 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). + * [ContinuationInterceptor] is specified, and adds optional support for + * copyable thread context [elements][CopyableThreadContextElement] and 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.foldCopiesForChildCoroutine() + context + val combined = foldCopies(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 } -/** - * Returns the [CoroutineContext] for a child coroutine to inherit. - * - * If any [CopyableThreadContextElement] is in the [this], calls - * [CopyableThreadContextElement.copyForChildCoroutine] on each, returning a new [CoroutineContext] - * by folding the returned copied elements into [this]. +private val hasCopyableElements: (Boolean, CoroutineContext.Element) -> Boolean = { result, it -> + result || it is CopyableThreadContextElement<*, *> +} + +/* + * Folds two contexts if there is need to. * - * Returns [this] if `this` has zero [CopyableThreadContextElement] in it. + * The algorithm is the following: + * * + * * + * * */ -private fun CoroutineContext.foldCopiesForChildCoroutine(): CoroutineContext { - val hasToCopy = fold(false) { result, it -> - result || it is CopyableThreadContextElement<*> +private fun CoroutineScope.foldCopies(appendContext: CoroutineContext): CoroutineContext { + // Do we have something to copy left-hand side? + val hasElementsLeft = coroutineContext.fold(false, hasCopyableElements) + val hasElementsRight = appendContext.fold(false, hasCopyableElements) + + // Nothing to fold, so just return the sum of contexts + if (!hasElementsLeft && !hasElementsRight) { + return coroutineContext + appendContext } - if (!hasToCopy) return this - return fold(EmptyCoroutineContext) { combined, it -> - combined + if (it is CopyableThreadContextElement<*>) it.copyForChildCoroutine() else it + + // Elements from 'appendContext' that TODO + var leftoverContext = appendContext + + val folded = coroutineContext.fold(EmptyCoroutineContext) { result, element -> + if (element !is CopyableThreadContextElement<*, *>) return@fold result + element + // Will this element be overwritten? + val newElement = leftoverContext[element.key] + // No, just copy it + if (newElement == null) { + return@fold result + element.copyForChildCoroutine() + } + // Yes, then first remove the element from append context + leftoverContext = leftoverContext.minusKey(element.key) + // Return the sum + @Suppress("UNCHECKED_CAST") + return@fold result + element.mergeUnsafe(newElement as CopyableThreadContextElement) } + + return folded + leftoverContext +} + +@Suppress("UNCHECKED_CAST") +private fun > CopyableThreadContextElement<*, *>.mergeUnsafe( + f: CopyableThreadContextElement<*, *> +): CoroutineContext.Element { + return (this as E).merge(f as E) } /** diff --git a/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt b/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt index 1a960699c7..81b79407ba 100644 --- a/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt +++ b/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt @@ -97,7 +97,7 @@ public interface ThreadContextElement : CoroutineContext.Element { * is in a coroutine: * * ``` - * class TraceContextElement(private val traceData: TraceData?) : CopyableThreadContextElement { + * class TraceContextElement(private val traceData: TraceData?) : CopyableThreadContextElement { * companion object Key : CoroutineContext.Key * override val key: CoroutineContext.Key = Key * @@ -111,12 +111,21 @@ public interface ThreadContextElement : CoroutineContext.Element { * traceThreadLocal.set(oldState) * } * - * override fun copyForChildCoroutine(): CopyableThreadContextElement { + * override fun copyForChildCoroutine(): TraceContextElement { * // Copy from the ThreadLocal source of truth at child coroutine launch time. This makes * // ThreadLocal writes between resumption of the parent coroutine and the launch of the * // child coroutine visible to the child. * return TraceContextElement(traceThreadLocal.get()?.copy()) * } + * + * override fun merge(element: TraceContextElement): TraceContextElement { + * // Merge operation defines how to handle situation when both + * // parent coroutine has the element in the context and it was also + * // explicitly passed to a child coroutine. + * + * // TODO in this example there is no mutable data at all, so merge just copies + * return TraceContextElement(traceThreadLocal.get()?.copy()) + * } * } * ``` * @@ -124,7 +133,7 @@ public interface ThreadContextElement : CoroutineContext.Element { * `Thread`. */ @ExperimentalCoroutinesApi -public interface CopyableThreadContextElement : ThreadContextElement { +public interface CopyableThreadContextElement> : ThreadContextElement { /** * Returns a [CopyableThreadContextElement] to replace `this` `CopyableThreadContextElement` in the child @@ -136,7 +145,9 @@ public interface CopyableThreadContextElement : ThreadContextElement { * Since this method is called whenever a new coroutine is launched in a context containing this * [CopyableThreadContextElement], implementations are performance-sensitive. */ - public fun copyForChildCoroutine(): CopyableThreadContextElement + public fun copyForChildCoroutine(): E + + public fun merge(element: E): E } /** diff --git a/kotlinx-coroutines-core/jvm/test/ThreadContextElementTest.kt b/kotlinx-coroutines-core/jvm/test/ThreadContextElementTest.kt index baba4aa8e6..153bc5e187 100644 --- a/kotlinx-coroutines-core/jvm/test/ThreadContextElementTest.kt +++ b/kotlinx-coroutines-core/jvm/test/ThreadContextElementTest.kt @@ -189,7 +189,7 @@ class MyElement(val data: MyData) : ThreadContextElement { /** * A [ThreadContextElement] that implements copy semantics in [copyForChildCoroutine]. */ -class CopyForChildCoroutineElement(val data: MyData?) : CopyableThreadContextElement { +class CopyForChildCoroutineElement(val data: MyData?) : CopyableThreadContextElement { companion object Key : CoroutineContext.Key override val key: CoroutineContext.Key @@ -201,6 +201,10 @@ class CopyForChildCoroutineElement(val data: MyData?) : CopyableThreadContextEle return oldState } + override fun merge(element: CopyForChildCoroutineElement): CopyForChildCoroutineElement { + TODO("Not yet implemented") + } + override fun restoreThreadContext(context: CoroutineContext, oldState: MyData?) { myThreadLocal.set(oldState) } @@ -216,7 +220,7 @@ class CopyForChildCoroutineElement(val data: MyData?) : CopyableThreadContextEle * will be reflected in the parent coroutine's [CopyForChildCoroutineElement] when it yields the * thread and calls [restoreThreadContext]. */ - override fun copyForChildCoroutine(): CopyableThreadContextElement { + override fun copyForChildCoroutine(): CopyForChildCoroutineElement { return CopyForChildCoroutineElement(myThreadLocal.get()) } } diff --git a/kotlinx-coroutines-core/jvm/test/ThreadContextMutableCopiesTest.kt b/kotlinx-coroutines-core/jvm/test/ThreadContextMutableCopiesTest.kt new file mode 100644 index 0000000000..7f3c39e5a1 --- /dev/null +++ b/kotlinx-coroutines-core/jvm/test/ThreadContextMutableCopiesTest.kt @@ -0,0 +1,113 @@ +/* + * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines + +import kotlin.coroutines.* +import kotlin.test.* + +class ThreadContextMutableCopiesTest : TestBase() { + companion object { + val threadLocalData: ThreadLocal> = ThreadLocal.withInitial { ArrayList() } + } + + class MyMutableElement( + private val mutableData: MutableList + ) : CopyableThreadContextElement, MyMutableElement> { + + companion object Key : CoroutineContext.Key + + override val key: CoroutineContext.Key<*> + get() = Key + + override fun updateThreadContext(context: CoroutineContext): MutableList { + val st = threadLocalData.get() + threadLocalData.set(mutableData) + return st + } + + override fun restoreThreadContext(context: CoroutineContext, oldState: MutableList) { + threadLocalData.set(oldState) + } + + override fun copyForChildCoroutine(): MyMutableElement { + return MyMutableElement(ArrayList(mutableData)) + } + + override fun merge(element: MyMutableElement): MyMutableElement { + return MyMutableElement((element.mutableData + mutableData).toSet().toMutableList()) + } + } + + @Test + fun testDataIsCopied() = runTest { + val root = MyMutableElement(ArrayList()) + runBlocking(root) { + val data = threadLocalData.get() + expect(1) + launch(root) { + assertNotSame(data, threadLocalData.get()) + assertEquals(data, threadLocalData.get()) + finish(2) + } + } + } + + @Test + fun testDataIsNotOverwritten() = runTest { + val root = MyMutableElement(ArrayList()) + runBlocking(root) { + expect(1) + val originalData = threadLocalData.get() + threadLocalData.get().add("X") + launch { + threadLocalData.get().add("Y") + // Note here, +root overwrites the data + launch(Dispatchers.Default + root) { + assertEquals(listOf("X", "Y"), threadLocalData.get().sorted()) + assertNotSame(originalData, threadLocalData.get()) + finish(2) + } + } + } + } + + @Test + fun testDataIsMerged() = runTest { + val root = MyMutableElement(ArrayList()) + runBlocking(root) { + expect(1) + val originalData = threadLocalData.get() + threadLocalData.get().add("X") + launch { + threadLocalData.get().add("Y") + // Note here, +root overwrites the data + launch(Dispatchers.Default + MyMutableElement(mutableListOf("Z"))) { + assertEquals(listOf("X", "Y", "Z"), threadLocalData.get().sorted()) + assertNotSame(originalData, threadLocalData.get()) + finish(2) + } + } + } + } + + @Test + @Ignore // Not implemented yet + fun testDataIsNotOverwrittenWithContext() = runTest { + val root = MyMutableElement(ArrayList()) + runBlocking(root) { + val originalData = threadLocalData.get() + threadLocalData.get().add("X") + launch { + threadLocalData.get().add("Y") + // Note here, +root overwrites the data + withContext(Dispatchers.Default + root) { + assertEquals(listOf("X", "Y"), threadLocalData.get().sorted()) + assertNotSame(originalData, threadLocalData.get()) + finish(2) + } + } + } + } +} From 091a3d7c788319928056d6fd4228fd93fc1b55bb Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Tue, 1 Feb 2022 15:18:32 +0300 Subject: [PATCH 02/26] CopyableThreadContextElement -- second iteration --- .../api/kotlinx-coroutines-core.api | 2 + .../common/src/Builders.common.kt | 2 +- .../common/src/CoroutineContext.common.kt | 2 + .../js/src/CoroutineContext.kt | 4 ++ .../jvm/src/CoroutineContext.kt | 51 ++++++++++--------- .../jvm/src/ThreadContextElement.kt | 17 +++++-- .../jvm/test/ThreadContextElementTest.kt | 11 ++-- .../test/ThreadContextMutableCopiesTest.kt | 37 +++++++++++--- .../native/src/CoroutineContext.kt | 4 ++ 9 files changed, 88 insertions(+), 42 deletions(-) diff --git a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api index d1fc624a5e..65e36cedc7 100644 --- a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api +++ b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api @@ -142,6 +142,7 @@ public final class kotlinx/coroutines/CompletionHandlerException : java/lang/Run public abstract interface class kotlinx/coroutines/CopyableThreadContextElement : kotlinx/coroutines/ThreadContextElement { public abstract fun copyForChildCoroutine ()Lkotlinx/coroutines/CopyableThreadContextElement; + public abstract fun merge (Lkotlin/coroutines/CoroutineContext$Element;)Lkotlin/coroutines/CoroutineContext; } public final class kotlinx/coroutines/CopyableThreadContextElement$DefaultImpls { @@ -156,6 +157,7 @@ public abstract interface class kotlinx/coroutines/CopyableThrowable { } public final class kotlinx/coroutines/CoroutineContextKt { + public static final fun newCoroutineContext (Lkotlin/coroutines/CoroutineContext;Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext; public static final fun newCoroutineContext (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext; } diff --git a/kotlinx-coroutines-core/common/src/Builders.common.kt b/kotlinx-coroutines-core/common/src/Builders.common.kt index a11ffe9eb4..b923755e5f 100644 --- a/kotlinx-coroutines-core/common/src/Builders.common.kt +++ b/kotlinx-coroutines-core/common/src/Builders.common.kt @@ -148,7 +148,7 @@ public suspend fun withContext( return suspendCoroutineUninterceptedOrReturn sc@ { uCont -> // compute new context val oldContext = uCont.context - val newContext = oldContext + context + val newContext = oldContext.newCoroutineContext(context) // always check for cancellation of new context newContext.ensureActive() // FAST PATH #1 -- new context is the same as the old one diff --git a/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt b/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt index da094e152d..1c101cec71 100644 --- a/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt +++ b/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt @@ -12,6 +12,8 @@ import kotlin.coroutines.* */ public expect fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext +public expect fun CoroutineContext.newCoroutineContext(addedContext: CoroutineContext): CoroutineContext + @PublishedApi @Suppress("PropertyName") internal expect val DefaultDelay: Delay diff --git a/kotlinx-coroutines-core/js/src/CoroutineContext.kt b/kotlinx-coroutines-core/js/src/CoroutineContext.kt index 95cb3c2964..8036c88a10 100644 --- a/kotlinx-coroutines-core/js/src/CoroutineContext.kt +++ b/kotlinx-coroutines-core/js/src/CoroutineContext.kt @@ -42,6 +42,10 @@ public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): combined + Dispatchers.Default else combined } +public actual fun CoroutineContext.newCoroutineContext(addedContext: CoroutineContext): CoroutineContext { + return this + addedContext +} + // No debugging facilities on JS internal actual inline fun withCoroutineContext(context: CoroutineContext, countOrElement: Any?, block: () -> T): T = block() internal actual inline fun withContinuationContext(continuation: Continuation<*>, countOrElement: Any?, block: () -> T): T = block() diff --git a/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt b/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt index 9ee2b1983b..cb2100e39b 100644 --- a/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt +++ b/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt @@ -14,66 +14,71 @@ import kotlin.coroutines.jvm.internal.CoroutineStackFrame * copyable thread context [elements][CopyableThreadContextElement] and 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 = foldCopies(context) + val combined = foldCopies(coroutineContext, context, true) val debug = if (DEBUG) combined + CoroutineId(COROUTINE_ID.incrementAndGet()) else combined return if (combined !== Dispatchers.Default && combined[ContinuationInterceptor] == null) debug + Dispatchers.Default else debug } +/** + * Creates context for coroutine builder functions that do not launch a new coroutine, + * but change current coroutine context, such as [withContext] and `runBlocking`. + */ +@ExperimentalCoroutinesApi +public actual fun CoroutineContext.newCoroutineContext(addedContext: CoroutineContext): CoroutineContext { + return foldCopies(this, addedContext, false) +} + private val hasCopyableElements: (Boolean, CoroutineContext.Element) -> Boolean = { result, it -> - result || it is CopyableThreadContextElement<*, *> + result || it is CopyableThreadContextElement<*> } /* * Folds two contexts if there is need to. - * - * The algorithm is the following: - * * - * * - * * */ -private fun CoroutineScope.foldCopies(appendContext: CoroutineContext): CoroutineContext { +private fun foldCopies(originalContext: CoroutineContext, appendContext: CoroutineContext, isNewCoroutine: Boolean): CoroutineContext { // Do we have something to copy left-hand side? - val hasElementsLeft = coroutineContext.fold(false, hasCopyableElements) + val hasElementsLeft = originalContext.fold(false, hasCopyableElements) val hasElementsRight = appendContext.fold(false, hasCopyableElements) // Nothing to fold, so just return the sum of contexts if (!hasElementsLeft && !hasElementsRight) { - return coroutineContext + appendContext + return originalContext + appendContext } - // Elements from 'appendContext' that TODO var leftoverContext = appendContext - val folded = coroutineContext.fold(EmptyCoroutineContext) { result, element -> - if (element !is CopyableThreadContextElement<*, *>) return@fold result + element + val folded = originalContext.fold(EmptyCoroutineContext) { result, element -> + if (element !is CopyableThreadContextElement<*>) return@fold result + element // Will this element be overwritten? val newElement = leftoverContext[element.key] // No, just copy it if (newElement == null) { - return@fold result + element.copyForChildCoroutine() + // For 'withContext'-like builders we do not copy as the element is not shared + return@fold result + if (isNewCoroutine) element.copyForChildCoroutine() else element } // Yes, then first remove the element from append context leftoverContext = leftoverContext.minusKey(element.key) // Return the sum @Suppress("UNCHECKED_CAST") - return@fold result + element.mergeUnsafe(newElement as CopyableThreadContextElement) + return@fold result + (element as CopyableThreadContextElement).merge(newElement) } + if (hasElementsRight) { + leftoverContext = leftoverContext.fold(EmptyCoroutineContext) { result, element -> + // We're appending new context element -- we have to copy it, otherwise it may be shared with others + if (element is CopyableThreadContextElement<*>) { + return@fold result + element.copyForChildCoroutine() + } + return@fold result + element + } + } return folded + leftoverContext } -@Suppress("UNCHECKED_CAST") -private fun > CopyableThreadContextElement<*, *>.mergeUnsafe( - f: CopyableThreadContextElement<*, *> -): CoroutineContext.Element { - return (this as E).merge(f as E) -} - /** * Executes a block using a given coroutine context. */ diff --git a/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt b/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt index 81b79407ba..7e8b87a122 100644 --- a/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt +++ b/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt @@ -133,11 +133,12 @@ public interface ThreadContextElement : CoroutineContext.Element { * `Thread`. */ @ExperimentalCoroutinesApi -public interface CopyableThreadContextElement> : ThreadContextElement { +public interface CopyableThreadContextElement : ThreadContextElement { /** * Returns a [CopyableThreadContextElement] to replace `this` `CopyableThreadContextElement` in the child - * coroutine's context that is under construction. + * coroutine's context that is under construction if the child coroutine's context does not contain + * an element with the same [key]. * * This function is called on the element each time a new coroutine inherits a context containing it, * and the returned value is folded into the context given to the child. @@ -145,9 +146,17 @@ public interface CopyableThreadContextElement - public fun merge(element: E): E + /** + * Returns a [CopyableThreadContextElement] to replace `this` `CopyableThreadContextElement` in the child + * coroutine's context when the child coroutine's context contains an element with the same [key] as the + * current one. + * + * This function is called on current element, supplied with an element retrieved from child's + * coroutine context by the current [key]. + */ + public fun merge(overwritingElement: CoroutineContext.Element): CoroutineContext } /** diff --git a/kotlinx-coroutines-core/jvm/test/ThreadContextElementTest.kt b/kotlinx-coroutines-core/jvm/test/ThreadContextElementTest.kt index 153bc5e187..15b8b3e920 100644 --- a/kotlinx-coroutines-core/jvm/test/ThreadContextElementTest.kt +++ b/kotlinx-coroutines-core/jvm/test/ThreadContextElementTest.kt @@ -126,8 +126,7 @@ class ThreadContextElementTest : TestBase() { @Test fun testCopyableThreadContextElementImplementsWriteVisibility() = runTest { newFixedThreadPoolContext(nThreads = 4, name = "withContext").use { - val startData = MyData() - withContext(it + CopyForChildCoroutineElement(startData)) { + withContext(it + CopyForChildCoroutineElement(MyData())) { val forBlockData = MyData() myThreadLocal.setForBlock(forBlockData) { assertSame(myThreadLocal.get(), forBlockData) @@ -153,7 +152,7 @@ class ThreadContextElementTest : TestBase() { assertSame(myThreadLocal.get(), forBlockData) } } - assertSame(myThreadLocal.get(), startData) // Asserts value was restored. + assertNull(myThreadLocal.get()) // Asserts value was restored to its origin } } } @@ -189,7 +188,7 @@ class MyElement(val data: MyData) : ThreadContextElement { /** * A [ThreadContextElement] that implements copy semantics in [copyForChildCoroutine]. */ -class CopyForChildCoroutineElement(val data: MyData?) : CopyableThreadContextElement { +class CopyForChildCoroutineElement(val data: MyData?) : CopyableThreadContextElement { companion object Key : CoroutineContext.Key override val key: CoroutineContext.Key @@ -201,8 +200,8 @@ class CopyForChildCoroutineElement(val data: MyData?) : CopyableThreadContextEle return oldState } - override fun merge(element: CopyForChildCoroutineElement): CopyForChildCoroutineElement { - TODO("Not yet implemented") + override fun merge(overwritingElement: CoroutineContext.Element): CopyForChildCoroutineElement { + TODO("Not used in tests") } override fun restoreThreadContext(context: CoroutineContext, oldState: MyData?) { diff --git a/kotlinx-coroutines-core/jvm/test/ThreadContextMutableCopiesTest.kt b/kotlinx-coroutines-core/jvm/test/ThreadContextMutableCopiesTest.kt index 7f3c39e5a1..2fde3ae48a 100644 --- a/kotlinx-coroutines-core/jvm/test/ThreadContextMutableCopiesTest.kt +++ b/kotlinx-coroutines-core/jvm/test/ThreadContextMutableCopiesTest.kt @@ -13,8 +13,8 @@ class ThreadContextMutableCopiesTest : TestBase() { } class MyMutableElement( - private val mutableData: MutableList - ) : CopyableThreadContextElement, MyMutableElement> { + val mutableData: MutableList + ) : CopyableThreadContextElement> { companion object Key : CoroutineContext.Key @@ -35,8 +35,9 @@ class ThreadContextMutableCopiesTest : TestBase() { return MyMutableElement(ArrayList(mutableData)) } - override fun merge(element: MyMutableElement): MyMutableElement { - return MyMutableElement((element.mutableData + mutableData).toSet().toMutableList()) + override fun merge(overwritingElement: CoroutineContext.Element): MyMutableElement { + overwritingElement as MyMutableElement // <- app-specific, may be another subtype + return MyMutableElement((mutableData.toSet() + overwritingElement.mutableData).toMutableList()) } } @@ -65,7 +66,7 @@ class ThreadContextMutableCopiesTest : TestBase() { threadLocalData.get().add("Y") // Note here, +root overwrites the data launch(Dispatchers.Default + root) { - assertEquals(listOf("X", "Y"), threadLocalData.get().sorted()) + assertEquals(listOf("X", "Y"), threadLocalData.get()) assertNotSame(originalData, threadLocalData.get()) finish(2) } @@ -84,7 +85,7 @@ class ThreadContextMutableCopiesTest : TestBase() { threadLocalData.get().add("Y") // Note here, +root overwrites the data launch(Dispatchers.Default + MyMutableElement(mutableListOf("Z"))) { - assertEquals(listOf("X", "Y", "Z"), threadLocalData.get().sorted()) + assertEquals(listOf("X", "Y", "Z"), threadLocalData.get()) assertNotSame(originalData, threadLocalData.get()) finish(2) } @@ -93,21 +94,41 @@ class ThreadContextMutableCopiesTest : TestBase() { } @Test - @Ignore // Not implemented yet fun testDataIsNotOverwrittenWithContext() = runTest { val root = MyMutableElement(ArrayList()) runBlocking(root) { val originalData = threadLocalData.get() threadLocalData.get().add("X") + expect(1) launch { threadLocalData.get().add("Y") // Note here, +root overwrites the data withContext(Dispatchers.Default + root) { - assertEquals(listOf("X", "Y"), threadLocalData.get().sorted()) + assertEquals(listOf("X", "Y"), threadLocalData.get()) assertNotSame(originalData, threadLocalData.get()) finish(2) } } } } + + @Test + fun testDataIsCopiedForRunBlocking() = runTest { + val root = MyMutableElement(ArrayList()) + val originalData = root.mutableData + runBlocking(root) { + assertNotSame(originalData, threadLocalData.get()) + } + } + + @Test + fun testDataIsCopiedForCoroutine() = runTest { + val root = MyMutableElement(ArrayList()) + val originalData = root.mutableData + expect(1) + launch(root) { + assertNotSame(originalData, threadLocalData.get()) + finish(2) + } + } } diff --git a/kotlinx-coroutines-core/native/src/CoroutineContext.kt b/kotlinx-coroutines-core/native/src/CoroutineContext.kt index e1e29581a7..6e2dac1a29 100644 --- a/kotlinx-coroutines-core/native/src/CoroutineContext.kt +++ b/kotlinx-coroutines-core/native/src/CoroutineContext.kt @@ -49,6 +49,10 @@ public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): combined + (DefaultDelay as CoroutineContext.Element) else combined } +public actual fun CoroutineContext.newCoroutineContext(addedContext: CoroutineContext): CoroutineContext { + return this + addedContext +} + // No debugging facilities on native internal actual inline fun withCoroutineContext(context: CoroutineContext, countOrElement: Any?, block: () -> T): T = block() internal actual inline fun withContinuationContext(continuation: Continuation<*>, countOrElement: Any?, block: () -> T): T = block() From 1857404d9ddd6d5eb48b8d40d899a4ab9226acaf Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Tue, 15 Feb 2022 18:28:20 +0300 Subject: [PATCH 03/26] Speed-up newCoroutineContext for 'withContext' which is the biggest potential regression hitter --- kotlinx-coroutines-core/jvm/src/CoroutineContext.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt b/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt index cb2100e39b..d984987eae 100644 --- a/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt +++ b/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt @@ -25,10 +25,16 @@ public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): /** * Creates context for coroutine builder functions that do not launch a new coroutine, - * but change current coroutine context, such as [withContext] and `runBlocking`. + * but change current coroutine context, such as [withContext]. */ @ExperimentalCoroutinesApi public actual fun CoroutineContext.newCoroutineContext(addedContext: CoroutineContext): CoroutineContext { + /* + * Fast-path: we only have to copy/merge if 'addedContext' (which typically has one or two elements) + * contains copyable element. + */ + if (!addedContext.fold(false, hasCopyableElements)) return this + addedContext + // TODO Here addedContext will be re-evaluated, we can fix it later when the design converges to its final form. return foldCopies(this, addedContext, false) } From 774031ec835c3936839615b40647ba061fd80749 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Tue, 15 Feb 2022 18:32:35 +0300 Subject: [PATCH 04/26] Better naming and @Delicate coroutines API --- kotlinx-coroutines-core/jvm/src/CoroutineContext.kt | 6 +++--- .../jvm/src/ThreadContextElement.kt | 12 ++++++------ .../jvm/test/ThreadContextElementTest.kt | 6 +++--- .../jvm/test/ThreadContextMutableCopiesTest.kt | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt b/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt index d984987eae..603c5aab95 100644 --- a/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt +++ b/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt @@ -64,20 +64,20 @@ private fun foldCopies(originalContext: CoroutineContext, appendContext: Corouti // No, just copy it if (newElement == null) { // For 'withContext'-like builders we do not copy as the element is not shared - return@fold result + if (isNewCoroutine) element.copyForChildCoroutine() else element + return@fold result + if (isNewCoroutine) element.copyForChild() else element } // Yes, then first remove the element from append context leftoverContext = leftoverContext.minusKey(element.key) // Return the sum @Suppress("UNCHECKED_CAST") - return@fold result + (element as CopyableThreadContextElement).merge(newElement) + return@fold result + (element as CopyableThreadContextElement).mergeForChild(newElement) } if (hasElementsRight) { leftoverContext = leftoverContext.fold(EmptyCoroutineContext) { result, element -> // We're appending new context element -- we have to copy it, otherwise it may be shared with others if (element is CopyableThreadContextElement<*>) { - return@fold result + element.copyForChildCoroutine() + return@fold result + element.copyForChild() } return@fold result + element } diff --git a/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt b/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt index 7e8b87a122..76505f1f20 100644 --- a/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt +++ b/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt @@ -111,19 +111,18 @@ public interface ThreadContextElement : CoroutineContext.Element { * traceThreadLocal.set(oldState) * } * - * override fun copyForChildCoroutine(): TraceContextElement { + * override fun copyForChild(): TraceContextElement { * // Copy from the ThreadLocal source of truth at child coroutine launch time. This makes * // ThreadLocal writes between resumption of the parent coroutine and the launch of the * // child coroutine visible to the child. * return TraceContextElement(traceThreadLocal.get()?.copy()) * } * - * override fun merge(element: TraceContextElement): TraceContextElement { + * override fun mergeForChild(overwritingElement: CoroutineContext.Element): CoroutineContext * // Merge operation defines how to handle situation when both * // parent coroutine has the element in the context and it was also * // explicitly passed to a child coroutine. - * - * // TODO in this example there is no mutable data at all, so merge just copies + * // If merge is not defined, the copy of the element can be returned. * return TraceContextElement(traceThreadLocal.get()?.copy()) * } * } @@ -132,6 +131,7 @@ public interface ThreadContextElement : CoroutineContext.Element { * A coroutine using this mechanism can safely call Java code that assumes it's called using a * `Thread`. */ +@DelicateCoroutinesApi @ExperimentalCoroutinesApi public interface CopyableThreadContextElement : ThreadContextElement { @@ -146,7 +146,7 @@ public interface CopyableThreadContextElement : ThreadContextElement { * Since this method is called whenever a new coroutine is launched in a context containing this * [CopyableThreadContextElement], implementations are performance-sensitive. */ - public fun copyForChildCoroutine(): CopyableThreadContextElement + public fun copyForChild(): CopyableThreadContextElement /** * Returns a [CopyableThreadContextElement] to replace `this` `CopyableThreadContextElement` in the child @@ -156,7 +156,7 @@ public interface CopyableThreadContextElement : ThreadContextElement { * This function is called on current element, supplied with an element retrieved from child's * coroutine context by the current [key]. */ - public fun merge(overwritingElement: CoroutineContext.Element): CoroutineContext + public fun mergeForChild(overwritingElement: CoroutineContext.Element): CoroutineContext } /** diff --git a/kotlinx-coroutines-core/jvm/test/ThreadContextElementTest.kt b/kotlinx-coroutines-core/jvm/test/ThreadContextElementTest.kt index 15b8b3e920..ec45406bce 100644 --- a/kotlinx-coroutines-core/jvm/test/ThreadContextElementTest.kt +++ b/kotlinx-coroutines-core/jvm/test/ThreadContextElementTest.kt @@ -186,7 +186,7 @@ class MyElement(val data: MyData) : ThreadContextElement { } /** - * A [ThreadContextElement] that implements copy semantics in [copyForChildCoroutine]. + * A [ThreadContextElement] that implements copy semantics in [copyForChild]. */ class CopyForChildCoroutineElement(val data: MyData?) : CopyableThreadContextElement { companion object Key : CoroutineContext.Key @@ -200,7 +200,7 @@ class CopyForChildCoroutineElement(val data: MyData?) : CopyableThreadContextEle return oldState } - override fun merge(overwritingElement: CoroutineContext.Element): CopyForChildCoroutineElement { + override fun mergeForChild(overwritingElement: CoroutineContext.Element): CopyForChildCoroutineElement { TODO("Not used in tests") } @@ -219,7 +219,7 @@ class CopyForChildCoroutineElement(val data: MyData?) : CopyableThreadContextEle * will be reflected in the parent coroutine's [CopyForChildCoroutineElement] when it yields the * thread and calls [restoreThreadContext]. */ - override fun copyForChildCoroutine(): CopyForChildCoroutineElement { + override fun copyForChild(): CopyForChildCoroutineElement { return CopyForChildCoroutineElement(myThreadLocal.get()) } } diff --git a/kotlinx-coroutines-core/jvm/test/ThreadContextMutableCopiesTest.kt b/kotlinx-coroutines-core/jvm/test/ThreadContextMutableCopiesTest.kt index 2fde3ae48a..4af52a372b 100644 --- a/kotlinx-coroutines-core/jvm/test/ThreadContextMutableCopiesTest.kt +++ b/kotlinx-coroutines-core/jvm/test/ThreadContextMutableCopiesTest.kt @@ -31,11 +31,11 @@ class ThreadContextMutableCopiesTest : TestBase() { threadLocalData.set(oldState) } - override fun copyForChildCoroutine(): MyMutableElement { + override fun copyForChild(): MyMutableElement { return MyMutableElement(ArrayList(mutableData)) } - override fun merge(overwritingElement: CoroutineContext.Element): MyMutableElement { + override fun mergeForChild(overwritingElement: CoroutineContext.Element): MyMutableElement { overwritingElement as MyMutableElement // <- app-specific, may be another subtype return MyMutableElement((mutableData.toSet() + overwritingElement.mutableData).toMutableList()) } From 8563d4637e57237dc79f4f88c4db05c387d1671f Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Wed, 16 Feb 2022 02:41:37 +0300 Subject: [PATCH 05/26] Update binary API dump --- kotlinx-coroutines-core/api/kotlinx-coroutines-core.api | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api index 65e36cedc7..79f3cf4308 100644 --- a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api +++ b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api @@ -141,8 +141,8 @@ public final class kotlinx/coroutines/CompletionHandlerException : java/lang/Run } public abstract interface class kotlinx/coroutines/CopyableThreadContextElement : kotlinx/coroutines/ThreadContextElement { - public abstract fun copyForChildCoroutine ()Lkotlinx/coroutines/CopyableThreadContextElement; - public abstract fun merge (Lkotlin/coroutines/CoroutineContext$Element;)Lkotlin/coroutines/CoroutineContext; + public abstract fun copyForChild ()Lkotlinx/coroutines/CopyableThreadContextElement; + public abstract fun mergeForChild (Lkotlin/coroutines/CoroutineContext$Element;)Lkotlin/coroutines/CoroutineContext; } public final class kotlinx/coroutines/CopyableThreadContextElement$DefaultImpls { From 7757b0673737bde0c3aacededf90f8cc57d45c39 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Wed, 30 Mar 2022 18:51:25 +0300 Subject: [PATCH 06/26] ~documentation tweaks --- .../common/src/Builders.common.kt | 1 + .../common/src/CoroutineContext.common.kt | 8 ++++++- .../jvm/src/CoroutineContext.kt | 21 ++++++++++++------- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/kotlinx-coroutines-core/common/src/Builders.common.kt b/kotlinx-coroutines-core/common/src/Builders.common.kt index b923755e5f..c360724245 100644 --- a/kotlinx-coroutines-core/common/src/Builders.common.kt +++ b/kotlinx-coroutines-core/common/src/Builders.common.kt @@ -148,6 +148,7 @@ public suspend fun withContext( return suspendCoroutineUninterceptedOrReturn sc@ { uCont -> // compute new context val oldContext = uCont.context + // Copy CopyableThreadContextElement if necessary val newContext = oldContext.newCoroutineContext(context) // always check for cancellation of new context newContext.ensureActive() diff --git a/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt b/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt index 1c101cec71..d51f68a8bf 100644 --- a/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt +++ b/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt @@ -8,10 +8,16 @@ import kotlin.coroutines.* /** * Creates a context for the new coroutine. It installs [Dispatchers.Default] when no other dispatcher or - * [ContinuationInterceptor] is specified, and adds optional support for debugging facilities (when turned on). + * [ContinuationInterceptor] is specified, and adds optional support for debugging facilities (when turned on) + * and copyable thread local facilities on JVM. */ public expect fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext +/** + * Creates context for coroutine builder functions that do not launch a new coroutine, namely [withContext]. + * @suppress + */ +@InternalCoroutinesApi public expect fun CoroutineContext.newCoroutineContext(addedContext: CoroutineContext): CoroutineContext @PublishedApi diff --git a/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt b/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt index 603c5aab95..cd8174f7ac 100644 --- a/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt +++ b/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt @@ -11,7 +11,7 @@ import kotlin.coroutines.jvm.internal.CoroutineStackFrame /** * Creates context for the new coroutine. It installs [Dispatchers.Default] when no other dispatcher nor * [ContinuationInterceptor] is specified, and adds optional support for - * copyable thread context [elements][CopyableThreadContextElement] and debugging facilities (when turned on). + * copyable thread context [elements][CopyableThreadContextElement] and debugging facilities (when turned on). * * See [DEBUG_PROPERTY_NAME] for description of debugging facilities on JVM. */ @@ -24,17 +24,16 @@ public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): } /** - * Creates context for coroutine builder functions that do not launch a new coroutine, - * but change current coroutine context, such as [withContext]. + * Creates context for coroutine builder functions that do not launch a new coroutine, namely [withContext]. + * @suppress */ -@ExperimentalCoroutinesApi +@InternalCoroutinesApi public actual fun CoroutineContext.newCoroutineContext(addedContext: CoroutineContext): CoroutineContext { /* * Fast-path: we only have to copy/merge if 'addedContext' (which typically has one or two elements) * contains copyable element. */ if (!addedContext.fold(false, hasCopyableElements)) return this + addedContext - // TODO Here addedContext will be re-evaluated, we can fix it later when the design converges to its final form. return foldCopies(this, addedContext, false) } @@ -42,8 +41,15 @@ private val hasCopyableElements: (Boolean, CoroutineContext.Element) -> Boolean result || it is CopyableThreadContextElement<*> } -/* - * Folds two contexts if there is need to. +/** + * Folds two contexts properly applying [CopyableThreadContextElement] rules when necessary. + * The rules are the following: + * * If both context do not have CTCE, the sum of two contexts is returned + * * Every CTCE from left-hand side context that does not have matching (by key) element from right-hand side context + * is [copied][CopyableThreadContextElement.copyForChild] + * * Every CTCE from left-hand side context that has matching element in right-hand side context is [merged][CopyableThreadContextElement.mergeForChild] + * * Every CTCE from right-hand side context that hasn't been merged is copied + * * Everything else is added to the resulting context as is. */ private fun foldCopies(originalContext: CoroutineContext, appendContext: CoroutineContext, isNewCoroutine: Boolean): CoroutineContext { // Do we have something to copy left-hand side? @@ -56,7 +62,6 @@ private fun foldCopies(originalContext: CoroutineContext, appendContext: Corouti } var leftoverContext = appendContext - val folded = originalContext.fold(EmptyCoroutineContext) { result, element -> if (element !is CopyableThreadContextElement<*>) return@fold result + element // Will this element be overwritten? From 027e267d0531307a0b00cb0395cbae777a6105b2 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Fri, 1 Apr 2022 14:21:37 +0300 Subject: [PATCH 07/26] Update kotlinx-coroutines-core/common/src/CoroutineContext.common.kt Co-authored-by: dkhalanskyjb <52952525+dkhalanskyjb@users.noreply.github.com> --- kotlinx-coroutines-core/common/src/CoroutineContext.common.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt b/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt index d51f68a8bf..2aaafd6ee2 100644 --- a/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt +++ b/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt @@ -9,7 +9,7 @@ import kotlin.coroutines.* /** * Creates a context for the new coroutine. It installs [Dispatchers.Default] when no other dispatcher or * [ContinuationInterceptor] is specified, and adds optional support for debugging facilities (when turned on) - * and copyable thread local facilities on JVM. + * and copyable-thread-local facilities on JVM. */ public expect fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext From 05479c94dd7293a1f42c149597631cf58d33660d Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Fri, 1 Apr 2022 14:21:44 +0300 Subject: [PATCH 08/26] Update kotlinx-coroutines-core/common/src/CoroutineContext.common.kt Co-authored-by: dkhalanskyjb <52952525+dkhalanskyjb@users.noreply.github.com> --- kotlinx-coroutines-core/common/src/CoroutineContext.common.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt b/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt index 2aaafd6ee2..dd19bcbab1 100644 --- a/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt +++ b/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt @@ -7,7 +7,7 @@ package kotlinx.coroutines import kotlin.coroutines.* /** - * Creates a context for the new coroutine. It installs [Dispatchers.Default] when no other dispatcher or + * Creates a context for a new coroutine. It installs [Dispatchers.Default] when no other dispatcher or * [ContinuationInterceptor] is specified, and adds optional support for debugging facilities (when turned on) * and copyable-thread-local facilities on JVM. */ From 1f7fa0bcd13450b688533fd36eeb305fbc40aac9 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Fri, 1 Apr 2022 14:21:54 +0300 Subject: [PATCH 09/26] Update kotlinx-coroutines-core/common/src/CoroutineContext.common.kt Co-authored-by: dkhalanskyjb <52952525+dkhalanskyjb@users.noreply.github.com> --- kotlinx-coroutines-core/common/src/CoroutineContext.common.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt b/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt index dd19bcbab1..cc9a3e0d70 100644 --- a/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt +++ b/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt @@ -8,7 +8,7 @@ import kotlin.coroutines.* /** * Creates a context for a new coroutine. It installs [Dispatchers.Default] when no other dispatcher or - * [ContinuationInterceptor] is specified, and adds optional support for debugging facilities (when turned on) + * [ContinuationInterceptor] is specified and adds optional support for debugging facilities (when turned on) * and copyable-thread-local facilities on JVM. */ public expect fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext From e9f31d934789a295173bf9f0e12af41e6aced1ee Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Fri, 1 Apr 2022 14:22:04 +0300 Subject: [PATCH 10/26] Update kotlinx-coroutines-core/jvm/src/CoroutineContext.kt Co-authored-by: dkhalanskyjb <52952525+dkhalanskyjb@users.noreply.github.com> --- kotlinx-coroutines-core/jvm/src/CoroutineContext.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt b/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt index cd8174f7ac..65c48cfd00 100644 --- a/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt +++ b/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt @@ -10,8 +10,8 @@ import kotlin.coroutines.jvm.internal.CoroutineStackFrame /** * Creates context for the new coroutine. It installs [Dispatchers.Default] when no other dispatcher nor - * [ContinuationInterceptor] is specified, and adds optional support for - * copyable thread context [elements][CopyableThreadContextElement] and debugging facilities (when turned on). + * [ContinuationInterceptor] is specified and adds support for + * copyable thread context [elements][CopyableThreadContextElement] and debugging facilities (optionally, when turned on). * * See [DEBUG_PROPERTY_NAME] for description of debugging facilities on JVM. */ From be8161a9ce95c56e561f2b5e079003e58174dc1b Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Fri, 1 Apr 2022 14:22:26 +0300 Subject: [PATCH 11/26] Update kotlinx-coroutines-core/common/src/CoroutineContext.common.kt Co-authored-by: dkhalanskyjb <52952525+dkhalanskyjb@users.noreply.github.com> --- kotlinx-coroutines-core/common/src/CoroutineContext.common.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt b/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt index cc9a3e0d70..9153f39821 100644 --- a/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt +++ b/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt @@ -14,7 +14,7 @@ import kotlin.coroutines.* public expect fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext /** - * Creates context for coroutine builder functions that do not launch a new coroutine, namely [withContext]. + * Creates a context for coroutine builder functions that do not launch a new coroutine, e.g. [withContext]. * @suppress */ @InternalCoroutinesApi From a85e8fb44865c9ec924a899805546f54c9c2d636 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Fri, 1 Apr 2022 14:23:23 +0300 Subject: [PATCH 12/26] Update kotlinx-coroutines-core/jvm/src/CoroutineContext.kt Co-authored-by: dkhalanskyjb <52952525+dkhalanskyjb@users.noreply.github.com> --- kotlinx-coroutines-core/jvm/src/CoroutineContext.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt b/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt index 65c48cfd00..a980e86575 100644 --- a/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt +++ b/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt @@ -31,7 +31,7 @@ public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): public actual fun CoroutineContext.newCoroutineContext(addedContext: CoroutineContext): CoroutineContext { /* * Fast-path: we only have to copy/merge if 'addedContext' (which typically has one or two elements) - * contains copyable element. + * contains copyable elements. */ if (!addedContext.fold(false, hasCopyableElements)) return this + addedContext return foldCopies(this, addedContext, false) From f572e8bd4c1490a65c215a7c5a760b6a04ae239f Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Fri, 1 Apr 2022 14:24:26 +0300 Subject: [PATCH 13/26] Update kotlinx-coroutines-core/jvm/src/CoroutineContext.kt Co-authored-by: dkhalanskyjb <52952525+dkhalanskyjb@users.noreply.github.com> --- kotlinx-coroutines-core/jvm/src/CoroutineContext.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt b/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt index a980e86575..33a6174275 100644 --- a/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt +++ b/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt @@ -44,7 +44,7 @@ private val hasCopyableElements: (Boolean, CoroutineContext.Element) -> Boolean /** * Folds two contexts properly applying [CopyableThreadContextElement] rules when necessary. * The rules are the following: - * * If both context do not have CTCE, the sum of two contexts is returned + * * If neither context has CTCE, the sum of two contexts is returned * * Every CTCE from left-hand side context that does not have matching (by key) element from right-hand side context * is [copied][CopyableThreadContextElement.copyForChild] * * Every CTCE from left-hand side context that has matching element in right-hand side context is [merged][CopyableThreadContextElement.mergeForChild] From ff5083b03617338ab3b7e3e6f2f2a6b4caa69674 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Fri, 1 Apr 2022 19:37:05 +0300 Subject: [PATCH 14/26] Update kotlinx-coroutines-core/jvm/src/CoroutineContext.kt Co-authored-by: dkhalanskyjb <52952525+dkhalanskyjb@users.noreply.github.com> --- kotlinx-coroutines-core/jvm/src/CoroutineContext.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt b/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt index 33a6174275..6d2e2dd48c 100644 --- a/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt +++ b/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt @@ -45,8 +45,8 @@ private val hasCopyableElements: (Boolean, CoroutineContext.Element) -> Boolean * Folds two contexts properly applying [CopyableThreadContextElement] rules when necessary. * The rules are the following: * * If neither context has CTCE, the sum of two contexts is returned - * * Every CTCE from left-hand side context that does not have matching (by key) element from right-hand side context - * is [copied][CopyableThreadContextElement.copyForChild] + * * Every CTCE from the left-hand side context that does not have a matching (by key) element from right-hand side context + * is [copied][CopyableThreadContextElement.copyForChild] if [isNewCoroutine] is `true`. * * Every CTCE from left-hand side context that has matching element in right-hand side context is [merged][CopyableThreadContextElement.mergeForChild] * * Every CTCE from right-hand side context that hasn't been merged is copied * * Everything else is added to the resulting context as is. From 31b57ad77c065cbfb7bffaf9820277b1a0a514b8 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Fri, 1 Apr 2022 19:37:12 +0300 Subject: [PATCH 15/26] Update kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt Co-authored-by: dkhalanskyjb <52952525+dkhalanskyjb@users.noreply.github.com> --- kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt b/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt index 76505f1f20..4ca3b9cc02 100644 --- a/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt +++ b/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt @@ -120,9 +120,11 @@ public interface ThreadContextElement : CoroutineContext.Element { * * override fun mergeForChild(overwritingElement: CoroutineContext.Element): CoroutineContext * // Merge operation defines how to handle situation when both - * // parent coroutine has the element in the context and it was also - * // explicitly passed to a child coroutine. - * // If merge is not defined, the copy of the element can be returned. + * // the parent coroutine has an element in the context and + * // an element with the same key was also + * // explicitly passed to the child coroutine. + * // If merging does not require special behavior, + * // the copy of the element can be returned. * return TraceContextElement(traceThreadLocal.get()?.copy()) * } * } From db0d6132eb56b94a28304073dbf228deb6c3e0c3 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Fri, 1 Apr 2022 19:37:17 +0300 Subject: [PATCH 16/26] Update kotlinx-coroutines-core/jvm/test/ThreadContextMutableCopiesTest.kt Co-authored-by: dkhalanskyjb <52952525+dkhalanskyjb@users.noreply.github.com> --- .../jvm/test/ThreadContextMutableCopiesTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kotlinx-coroutines-core/jvm/test/ThreadContextMutableCopiesTest.kt b/kotlinx-coroutines-core/jvm/test/ThreadContextMutableCopiesTest.kt index 4af52a372b..34e5955fd7 100644 --- a/kotlinx-coroutines-core/jvm/test/ThreadContextMutableCopiesTest.kt +++ b/kotlinx-coroutines-core/jvm/test/ThreadContextMutableCopiesTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ package kotlinx.coroutines From 20bedf9314cb883a7d7d86d4aff8db45ab805e78 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Fri, 1 Apr 2022 19:37:33 +0300 Subject: [PATCH 17/26] Update kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt Co-authored-by: dkhalanskyjb <52952525+dkhalanskyjb@users.noreply.github.com> --- kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt b/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt index 4ca3b9cc02..5c9db3dead 100644 --- a/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt +++ b/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt @@ -155,8 +155,8 @@ public interface CopyableThreadContextElement : ThreadContextElement { * coroutine's context when the child coroutine's context contains an element with the same [key] as the * current one. * - * This function is called on current element, supplied with an element retrieved from child's - * coroutine context by the current [key]. + * This method is invoked on the original element, accepting as the parameter + * the element that is supposed to overwrite it. */ public fun mergeForChild(overwritingElement: CoroutineContext.Element): CoroutineContext } From 479481eaa14827fff920ae0b1487d704da6cbb28 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Fri, 1 Apr 2022 19:38:40 +0300 Subject: [PATCH 18/26] Update kotlinx-coroutines-core/jvm/src/CoroutineContext.kt Co-authored-by: dkhalanskyjb <52952525+dkhalanskyjb@users.noreply.github.com> --- kotlinx-coroutines-core/jvm/src/CoroutineContext.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt b/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt index 6d2e2dd48c..1dfff9ce86 100644 --- a/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt +++ b/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt @@ -48,7 +48,7 @@ private val hasCopyableElements: (Boolean, CoroutineContext.Element) -> Boolean * * Every CTCE from the left-hand side context that does not have a matching (by key) element from right-hand side context * is [copied][CopyableThreadContextElement.copyForChild] if [isNewCoroutine] is `true`. * * Every CTCE from left-hand side context that has matching element in right-hand side context is [merged][CopyableThreadContextElement.mergeForChild] - * * Every CTCE from right-hand side context that hasn't been merged is copied + * * Every CTCE from the right-hand side context that hasn't been merged is copied * * Everything else is added to the resulting context as is. */ private fun foldCopies(originalContext: CoroutineContext, appendContext: CoroutineContext, isNewCoroutine: Boolean): CoroutineContext { From a32036ccce84d789ba5af236dd01c67b074412cb Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Fri, 1 Apr 2022 19:38:45 +0300 Subject: [PATCH 19/26] Update kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt Co-authored-by: dkhalanskyjb <52952525+dkhalanskyjb@users.noreply.github.com> --- kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt b/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt index 5c9db3dead..8cf594c1c8 100644 --- a/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt +++ b/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt @@ -152,8 +152,7 @@ public interface CopyableThreadContextElement : ThreadContextElement { /** * Returns a [CopyableThreadContextElement] to replace `this` `CopyableThreadContextElement` in the child - * coroutine's context when the child coroutine's context contains an element with the same [key] as the - * current one. + * coroutine's context when the child coroutine's context contains an element with the same [key]. * * This method is invoked on the original element, accepting as the parameter * the element that is supposed to overwrite it. From 64163c127f6254859a1b2cf5231d4d70ad337f5b Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Fri, 1 Apr 2022 19:38:54 +0300 Subject: [PATCH 20/26] Update kotlinx-coroutines-core/jvm/src/CoroutineContext.kt Co-authored-by: dkhalanskyjb <52952525+dkhalanskyjb@users.noreply.github.com> --- kotlinx-coroutines-core/jvm/src/CoroutineContext.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt b/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt index 1dfff9ce86..84679f5eaf 100644 --- a/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt +++ b/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt @@ -47,7 +47,7 @@ private val hasCopyableElements: (Boolean, CoroutineContext.Element) -> Boolean * * If neither context has CTCE, the sum of two contexts is returned * * Every CTCE from the left-hand side context that does not have a matching (by key) element from right-hand side context * is [copied][CopyableThreadContextElement.copyForChild] if [isNewCoroutine] is `true`. - * * Every CTCE from left-hand side context that has matching element in right-hand side context is [merged][CopyableThreadContextElement.mergeForChild] + * * Every CTCE from the left-hand side context that has a matching element in the right-hand side context is [merged][CopyableThreadContextElement.mergeForChild] * * Every CTCE from the right-hand side context that hasn't been merged is copied * * Everything else is added to the resulting context as is. */ From 9750b592199e66f49f0bfcafe04d3e69b0b452c0 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Fri, 1 Apr 2022 19:43:29 +0300 Subject: [PATCH 21/26] ~small tweaks --- .../jvm/src/CoroutineContext.kt | 26 +++++++++---------- .../jvm/src/ThreadContextElement.kt | 7 ++--- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt b/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt index 84679f5eaf..f1c4f0794c 100644 --- a/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt +++ b/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt @@ -9,10 +9,9 @@ import kotlin.coroutines.* import kotlin.coroutines.jvm.internal.CoroutineStackFrame /** - * Creates context for the new coroutine. It installs [Dispatchers.Default] when no other dispatcher nor - * [ContinuationInterceptor] is specified and adds support for - * copyable thread context [elements][CopyableThreadContextElement] and debugging facilities (optionally, when turned on). - * + * Creates a context for a new coroutine. It installs [Dispatchers.Default] when no other dispatcher or + * [ContinuationInterceptor] is specified and adds optional support for debugging facilities (when turned on) + * and copyable-thread-local facilities on JVM. * See [DEBUG_PROPERTY_NAME] for description of debugging facilities on JVM. */ @ExperimentalCoroutinesApi @@ -24,7 +23,7 @@ public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): } /** - * Creates context for coroutine builder functions that do not launch a new coroutine, namely [withContext]. + * Creates a context for coroutine builder functions that do not launch a new coroutine, e.g. [withContext]. * @suppress */ @InternalCoroutinesApi @@ -33,13 +32,12 @@ public actual fun CoroutineContext.newCoroutineContext(addedContext: CoroutineCo * Fast-path: we only have to copy/merge if 'addedContext' (which typically has one or two elements) * contains copyable elements. */ - if (!addedContext.fold(false, hasCopyableElements)) return this + addedContext + if (!addedContext.hasCopyableElements()) return this + addedContext return foldCopies(this, addedContext, false) } -private val hasCopyableElements: (Boolean, CoroutineContext.Element) -> Boolean = { result, it -> - result || it is CopyableThreadContextElement<*> -} +private fun CoroutineContext.hasCopyableElements(): Boolean = + fold(false) { result, it -> result || it is CopyableThreadContextElement<*> } /** * Folds two contexts properly applying [CopyableThreadContextElement] rules when necessary. @@ -53,8 +51,8 @@ private val hasCopyableElements: (Boolean, CoroutineContext.Element) -> Boolean */ private fun foldCopies(originalContext: CoroutineContext, appendContext: CoroutineContext, isNewCoroutine: Boolean): CoroutineContext { // Do we have something to copy left-hand side? - val hasElementsLeft = originalContext.fold(false, hasCopyableElements) - val hasElementsRight = appendContext.fold(false, hasCopyableElements) + val hasElementsLeft = originalContext.hasCopyableElements() + val hasElementsRight = appendContext.hasCopyableElements() // Nothing to fold, so just return the sum of contexts if (!hasElementsLeft && !hasElementsRight) { @@ -126,7 +124,7 @@ internal actual inline fun withContinuationContext(continuation: Continuatio 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. + * Fast-path to detect whether we have undispatched 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: @@ -137,8 +135,8 @@ internal fun Continuation<*>.updateUndispatchedCompletion(context: CoroutineCont * 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 potentiallyHasUndispatchedCoroutine = context[UndispatchedMarker] !== null + if (!potentiallyHasUndispatchedCoroutine) return null val completion = undispatchedCompletion() completion?.saveThreadContext(context, oldValue) return completion diff --git a/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt b/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt index 8cf594c1c8..36388209b9 100644 --- a/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt +++ b/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt @@ -80,7 +80,7 @@ public interface ThreadContextElement : CoroutineContext.Element { /** * A [ThreadContextElement] copied whenever a child coroutine inherits a context containing it. * - * When an API uses a _mutable_ `ThreadLocal` for consistency, a [CopyableThreadContextElement] + * When an API uses a _mutable_ [ThreadLocal] for consistency, a [CopyableThreadContextElement] * can give coroutines "coroutine-safe" write access to that `ThreadLocal`. * * A write made to a `ThreadLocal` with a matching [CopyableThreadContextElement] by a coroutine @@ -97,8 +97,9 @@ public interface ThreadContextElement : CoroutineContext.Element { * is in a coroutine: * * ``` - * class TraceContextElement(private val traceData: TraceData?) : CopyableThreadContextElement { + * class TraceContextElement(private val traceData: TraceData?) : CopyableThreadContextElement { * companion object Key : CoroutineContext.Key + * * override val key: CoroutineContext.Key = Key * * override fun updateThreadContext(context: CoroutineContext): TraceData? { @@ -118,7 +119,7 @@ public interface ThreadContextElement : CoroutineContext.Element { * return TraceContextElement(traceThreadLocal.get()?.copy()) * } * - * override fun mergeForChild(overwritingElement: CoroutineContext.Element): CoroutineContext + * override fun mergeForChild(overwritingElement: CoroutineContext.Element): CoroutineContext { * // Merge operation defines how to handle situation when both * // the parent coroutine has an element in the context and * // an element with the same key was also From 5360968f214b25c1fc2fde709e2d50756ba98201 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Fri, 1 Apr 2022 21:16:46 +0300 Subject: [PATCH 22/26] Update kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt Co-authored-by: dkhalanskyjb <52952525+dkhalanskyjb@users.noreply.github.com> --- kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt b/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt index 36388209b9..5c2d84f2e7 100644 --- a/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt +++ b/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt @@ -120,7 +120,7 @@ public interface ThreadContextElement : CoroutineContext.Element { * } * * override fun mergeForChild(overwritingElement: CoroutineContext.Element): CoroutineContext { - * // Merge operation defines how to handle situation when both + * // Merge operation defines how to handle situations when both * // the parent coroutine has an element in the context and * // an element with the same key was also * // explicitly passed to the child coroutine. From 20a0da8fbacb2b3a7c299224e1a498606fb0523d Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Sat, 2 Apr 2022 16:18:46 +0300 Subject: [PATCH 23/26] ~clarify doc sentence --- kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt b/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt index 5c2d84f2e7..81218361d7 100644 --- a/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt +++ b/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt @@ -131,8 +131,8 @@ public interface ThreadContextElement : CoroutineContext.Element { * } * ``` * - * A coroutine using this mechanism can safely call Java code that assumes it's called using a - * `Thread`. + * A coroutine using this mechanism can safely call Java code that assumes the corresponding thread local element's + * value is installed into the target thread local. */ @DelicateCoroutinesApi @ExperimentalCoroutinesApi From e9f02f19dbded457545bd6b484126657ff9a284b Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Mon, 4 Apr 2022 11:38:52 +0300 Subject: [PATCH 24/26] Update kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt Co-authored-by: dkhalanskyjb <52952525+dkhalanskyjb@users.noreply.github.com> --- kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt b/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt index 81218361d7..1227b1de43 100644 --- a/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt +++ b/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt @@ -140,7 +140,7 @@ public interface CopyableThreadContextElement : ThreadContextElement { /** * Returns a [CopyableThreadContextElement] to replace `this` `CopyableThreadContextElement` in the child - * coroutine's context that is under construction if the child coroutine's context does not contain + * coroutine's context that is under construction if the added context does not contain an element with the same key. * an element with the same [key]. * * This function is called on the element each time a new coroutine inherits a context containing it, From 9dfb8687b42f4bef638b4e7bf80749b6577d3cc4 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Mon, 4 Apr 2022 11:41:05 +0300 Subject: [PATCH 25/26] ~clarify doc sentence --- kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt b/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt index 1227b1de43..24ad932b44 100644 --- a/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt +++ b/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt @@ -153,7 +153,8 @@ public interface CopyableThreadContextElement : ThreadContextElement { /** * Returns a [CopyableThreadContextElement] to replace `this` `CopyableThreadContextElement` in the child - * coroutine's context when the child coroutine's context contains an element with the same [key]. + * coroutine's context that is under construction if the added context does contain an element with the same key. + * an element with the same [key]. * * This method is invoked on the original element, accepting as the parameter * the element that is supposed to overwrite it. From 60c43c9824dab7b5035313e560a3eebe9b4e7685 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Mon, 4 Apr 2022 12:40:47 +0300 Subject: [PATCH 26/26] ~ --- kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt b/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt index 24ad932b44..d2b6b6b988 100644 --- a/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt +++ b/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt @@ -140,8 +140,7 @@ public interface CopyableThreadContextElement : ThreadContextElement { /** * Returns a [CopyableThreadContextElement] to replace `this` `CopyableThreadContextElement` in the child - * coroutine's context that is under construction if the added context does not contain an element with the same key. - * an element with the same [key]. + * coroutine's context that is under construction if the added context does not contain an element with the same [key]. * * This function is called on the element each time a new coroutine inherits a context containing it, * and the returned value is folded into the context given to the child. @@ -153,8 +152,7 @@ public interface CopyableThreadContextElement : ThreadContextElement { /** * Returns a [CopyableThreadContextElement] to replace `this` `CopyableThreadContextElement` in the child - * coroutine's context that is under construction if the added context does contain an element with the same key. - * an element with the same [key]. + * coroutine's context that is under construction if the added context does contain an element with the same [key]. * * This method is invoked on the original element, accepting as the parameter * the element that is supposed to overwrite it.