Skip to content

Commit 54b6c97

Browse files
knishtvsalavatov
authored andcommitted
PR #7 Move ThreadContextElement to common
(cherry picked from commit 0634a77) # Conflicts: # kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt
1 parent a57712f commit 54b6c97

File tree

12 files changed

+533
-149
lines changed

12 files changed

+533
-149
lines changed

IntelliJ-patches.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,8 @@ Why not just let `next` return a maybe wrapped value? That's because it is heavi
8989
Changes were made to lambda parameter `onElementRetrieved` in `BufferedChannel<E>` methods: now they accept `Any?` instead of `E` because now they may be given a wrapped value.
9090

9191
`SelectImplementation.complete` now uses `debuggerCapture` to properly propagate value that might come from flows.
92+
93+
## `ThreadContextElement` is moved to common
94+
95+
The motivation of this change is enabling `ThreadContextElements` in Kotlin/Native, Kotlin/JS and Kotlin/wasm.
96+
The API is left intact.
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package kotlinx.coroutines
2+
3+
import kotlin.coroutines.*
4+
5+
/**
6+
* Defines elements in a [CoroutineContext] that are installed into the thread context
7+
* every time the coroutine with this element in the context is resumed on a thread.
8+
*
9+
* Implementations of this interface define a type [S] of the thread-local state that they need to store
10+
* upon resuming a coroutine and restore later upon suspension.
11+
* The infrastructure provides the corresponding storage.
12+
*
13+
* Example usage looks like this:
14+
*
15+
* ```
16+
* // Appends "name" of a coroutine to a current thread name when coroutine is executed
17+
* class CoroutineName(val name: String) : ThreadContextElement<String> {
18+
* // declare companion object for a key of this element in coroutine context
19+
* companion object Key : CoroutineContext.Key<CoroutineName>
20+
*
21+
* // provide the key of the corresponding context element
22+
* override val key: CoroutineContext.Key<CoroutineName>
23+
* get() = Key
24+
*
25+
* // this is invoked before coroutine is resumed on current thread
26+
* override fun updateThreadContext(context: CoroutineContext): String {
27+
* val previousName = Thread.currentThread().name
28+
* Thread.currentThread().name = "$previousName # $name"
29+
* return previousName
30+
* }
31+
*
32+
* // this is invoked after coroutine has suspended on current thread
33+
* override fun restoreThreadContext(context: CoroutineContext, oldState: String) {
34+
* Thread.currentThread().name = oldState
35+
* }
36+
* }
37+
*
38+
* // Usage
39+
* launch(Dispatchers.Main + CoroutineName("Progress bar coroutine")) { ... }
40+
* ```
41+
*
42+
* Every time this coroutine is resumed on a thread, UI thread name is updated to
43+
* "UI thread original name # Progress bar coroutine" and the thread name is restored to the original one when
44+
* this coroutine suspends.
45+
*
46+
* To use [ThreadLocal] variable within the coroutine use [ThreadLocal.asContextElement][asContextElement] function.
47+
*
48+
* ### Reentrancy and thread-safety
49+
*
50+
* Correct implementations of this interface must expect that calls to [restoreThreadContext]
51+
* may happen in parallel to the subsequent [updateThreadContext] and [restoreThreadContext] operations.
52+
* See [CopyableThreadContextElement] for advanced interleaving details.
53+
*
54+
* All implementations of [ThreadContextElement] should be thread-safe and guard their internal mutable state
55+
* within an element accordingly.
56+
*/
57+
public interface ThreadContextElement<S> : CoroutineContext.Element {
58+
/**
59+
* Updates context of the current thread.
60+
* This function is invoked before the coroutine in the specified [context] is resumed in the current thread
61+
* when the context of the coroutine this element.
62+
* The result of this function is the old value of the thread-local state that will be passed to [restoreThreadContext].
63+
* This method should handle its own exceptions and do not rethrow it. Thrown exceptions will leave coroutine which
64+
* context is updated in an undefined state and may crash an application.
65+
*
66+
* @param context the coroutine context.
67+
*/
68+
public fun updateThreadContext(context: CoroutineContext): S
69+
70+
/**
71+
* Restores context of the current thread.
72+
* This function is invoked after the coroutine in the specified [context] is suspended in the current thread
73+
* if [updateThreadContext] was previously invoked on resume of this coroutine.
74+
* The value of [oldState] is the result of the previous invocation of [updateThreadContext] and it should
75+
* be restored in the thread-local state by this function.
76+
* This method should handle its own exceptions and do not rethrow it. Thrown exceptions will leave coroutine which
77+
* context is updated in an undefined state and may crash an application.
78+
*
79+
* @param context the coroutine context.
80+
* @param oldState the value returned by the previous invocation of [updateThreadContext].
81+
*/
82+
public fun restoreThreadContext(context: CoroutineContext, oldState: S)
83+
}
Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,56 @@
11
package kotlinx.coroutines.internal
22

3+
import kotlinx.coroutines.*
34
import kotlin.coroutines.*
5+
import kotlin.jvm.*
46

5-
internal expect fun threadContextElements(context: CoroutineContext): Any
7+
@JvmField
8+
internal val NO_THREAD_ELEMENTS = Symbol("NO_THREAD_ELEMENTS")
9+
10+
// Used when there are >= 2 active elements in the context
11+
@Suppress("UNCHECKED_CAST")
12+
internal class ThreadState(@JvmField val context: CoroutineContext, n: Int) {
13+
private val values = arrayOfNulls<Any>(n)
14+
private val elements = arrayOfNulls<ThreadContextElement<Any?>>(n)
15+
private var i = 0
16+
17+
fun append(element: ThreadContextElement<*>, value: Any?) {
18+
values[i] = value
19+
elements[i++] = element as ThreadContextElement<Any?>
20+
}
21+
22+
fun restore(context: CoroutineContext) {
23+
for (i in elements.indices.reversed()) {
24+
elements[i]!!.restoreThreadContext(context, values[i])
25+
}
26+
}
27+
}
28+
29+
// Counts ThreadContextElements in the context
30+
// Any? here is Int | ThreadContextElement (when count is one)
31+
private val countAll =
32+
fun (countOrElement: Any?, element: CoroutineContext.Element): Any? {
33+
if (element is ThreadContextElement<*>) {
34+
val inCount = countOrElement as? Int ?: 1
35+
return if (inCount == 0) element else inCount + 1
36+
}
37+
return countOrElement
38+
}
39+
40+
// Find one (first) ThreadContextElement in the context, it is used when we know there is exactly one
41+
internal val findOne =
42+
fun (found: ThreadContextElement<*>?, element: CoroutineContext.Element): ThreadContextElement<*>? {
43+
if (found != null) return found
44+
return element as? ThreadContextElement<*>
45+
}
46+
47+
// Updates state for ThreadContextElements in the context using the given ThreadState
48+
internal val updateState =
49+
fun (state: ThreadState, element: CoroutineContext.Element): ThreadState {
50+
if (element is ThreadContextElement<*>) {
51+
state.append(element, element.updateThreadContext(state.context))
52+
}
53+
return state
54+
}
55+
56+
internal fun threadContextElements(context: CoroutineContext): Any = context.fold(0, countAll)!!

kotlinx-coroutines-core/js/src/CoroutineContext.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,26 @@
11
package kotlinx.coroutines
22

33
import kotlinx.browser.*
4+
import kotlinx.coroutines.internal.ScopeCoroutine
5+
import kotlin.coroutines.Continuation
6+
import kotlin.coroutines.CoroutineContext
47

58
private external val navigator: dynamic
69
private const val UNDEFINED = "undefined"
710
internal external val process: dynamic
811

12+
13+
// No debugging facilities on Wasm and JS
14+
internal actual inline fun <T> withCoroutineContext(context: CoroutineContext, countOrElement: Any?, block: () -> T): T = block()
15+
internal actual inline fun <T> withContinuationContext(continuation: Continuation<*>, countOrElement: Any?, block: () -> T): T = block()
16+
17+
internal actual class UndispatchedCoroutine<in T> actual constructor(
18+
context: CoroutineContext,
19+
uCont: Continuation<T>
20+
) : ScopeCoroutine<T>(context, uCont) {
21+
override fun afterResume(state: Any?) = uCont.resumeWith(recoverResult(state, uCont))
22+
}
23+
924
internal actual fun createDefaultDispatcher(): CoroutineDispatcher = when {
1025
// Check if we are running under jsdom. WindowDispatcher doesn't work under jsdom because it accesses MessageEvent#source.
1126
// It is not implemented in jsdom, see https://github.com/jsdom/jsdom/blob/master/Changelog.md

kotlinx-coroutines-core/jsAndWasmShared/src/CoroutineContext.kt

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,7 @@ public actual fun CoroutineContext.newCoroutineContext(addedContext: CoroutineCo
1717
return this + addedContext
1818
}
1919

20-
// No debugging facilities on Wasm and JS
21-
internal actual inline fun <T> withCoroutineContext(context: CoroutineContext, countOrElement: Any?, block: () -> T): T = block()
22-
internal actual inline fun <T> withContinuationContext(continuation: Continuation<*>, countOrElement: Any?, block: () -> T): T = block()
2320
internal actual fun Continuation<*>.toDebugString(): String = toString()
2421
internal actual val CoroutineContext.coroutineName: String? get() = null // not supported on Wasm and JS
2522

26-
internal actual class UndispatchedCoroutine<in T> actual constructor(
27-
context: CoroutineContext,
28-
uCont: Continuation<T>
29-
) : ScopeCoroutine<T>(context, uCont) {
30-
override fun afterResume(state: Any?) = uCont.resumeWith(recoverResult(state, uCont))
31-
}
32-
3323
internal actual inline fun <T> withThreadLocalContext(context: CoroutineContext, block: () -> T) : T = block()
Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,41 @@
11
package kotlinx.coroutines.internal
22

3+
import kotlinx.coroutines.*
34
import kotlin.coroutines.*
45

5-
internal actual fun threadContextElements(context: CoroutineContext): Any = 0
6+
// countOrElement is pre-cached in dispatched continuation
7+
// returns NO_THREAD_ELEMENTS if the contest does not have any ThreadContextElements
8+
internal fun updateThreadContext(context: CoroutineContext, countOrElement: Any?): Any? {
9+
@Suppress("NAME_SHADOWING")
10+
val countOrElement = countOrElement ?: threadContextElements(context)
11+
@Suppress("IMPLICIT_BOXING_IN_IDENTITY_EQUALS")
12+
return when {
13+
countOrElement == 0 -> NO_THREAD_ELEMENTS // very fast path when there are no active ThreadContextElements
14+
countOrElement is Int -> {
15+
// slow path for multiple active ThreadContextElements, allocates ThreadState for multiple old values
16+
context.fold(ThreadState(context, countOrElement), updateState)
17+
}
18+
else -> {
19+
// fast path for one ThreadContextElement (no allocations, no additional context scan)
20+
@Suppress("UNCHECKED_CAST")
21+
val element = countOrElement as ThreadContextElement<Any?>
22+
element.updateThreadContext(context)
23+
}
24+
}
25+
}
26+
27+
internal fun restoreThreadContext(context: CoroutineContext, oldState: Any?) {
28+
when {
29+
oldState === NO_THREAD_ELEMENTS -> return // very fast path when there are no ThreadContextElements
30+
oldState is ThreadState -> {
31+
// slow path with multiple stored ThreadContextElements
32+
oldState.restore(context)
33+
}
34+
else -> {
35+
// fast path for one ThreadContextElement, but need to find it
36+
@Suppress("UNCHECKED_CAST")
37+
val element = context.fold(null, findOne) as ThreadContextElement<Any?>
38+
element.restoreThreadContext(context, oldState)
39+
}
40+
}
41+
}

kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt

Lines changed: 0 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -3,86 +3,6 @@ package kotlinx.coroutines
33
import kotlinx.coroutines.internal.*
44
import kotlin.coroutines.*
55

6-
/**
7-
* Defines elements in a [CoroutineContext] that are installed into the thread context
8-
* every time the coroutine with this element in the context is resumed on a thread.
9-
*
10-
* Implementations of this interface define a type [S] of the thread-local state that they need to store
11-
* upon resuming a coroutine and restore later upon suspension.
12-
* The infrastructure provides the corresponding storage.
13-
*
14-
* Example usage looks like this:
15-
*
16-
* ```
17-
* // Appends "name" of a coroutine to a current thread name when coroutine is executed
18-
* class CoroutineName(val name: String) : ThreadContextElement<String> {
19-
* // declare companion object for a key of this element in coroutine context
20-
* companion object Key : CoroutineContext.Key<CoroutineName>
21-
*
22-
* // provide the key of the corresponding context element
23-
* override val key: CoroutineContext.Key<CoroutineName>
24-
* get() = Key
25-
*
26-
* // this is invoked before coroutine is resumed on current thread
27-
* override fun updateThreadContext(context: CoroutineContext): String {
28-
* val previousName = Thread.currentThread().name
29-
* Thread.currentThread().name = "$previousName # $name"
30-
* return previousName
31-
* }
32-
*
33-
* // this is invoked after coroutine has suspended on current thread
34-
* override fun restoreThreadContext(context: CoroutineContext, oldState: String) {
35-
* Thread.currentThread().name = oldState
36-
* }
37-
* }
38-
*
39-
* // Usage
40-
* launch(Dispatchers.Main + CoroutineName("Progress bar coroutine")) { ... }
41-
* ```
42-
*
43-
* Every time this coroutine is resumed on a thread, UI thread name is updated to
44-
* "UI thread original name # Progress bar coroutine" and the thread name is restored to the original one when
45-
* this coroutine suspends.
46-
*
47-
* To use [ThreadLocal] variable within the coroutine use [ThreadLocal.asContextElement][asContextElement] function.
48-
*
49-
* ### Reentrancy and thread-safety
50-
*
51-
* Correct implementations of this interface must expect that calls to [restoreThreadContext]
52-
* may happen in parallel to the subsequent [updateThreadContext] and [restoreThreadContext] operations.
53-
* See [CopyableThreadContextElement] for advanced interleaving details.
54-
*
55-
* All implementations of [ThreadContextElement] should be thread-safe and guard their internal mutable state
56-
* within an element accordingly.
57-
*/
58-
public interface ThreadContextElement<S> : CoroutineContext.Element {
59-
/**
60-
* Updates context of the current thread.
61-
* This function is invoked before the coroutine in the specified [context] is resumed in the current thread
62-
* when the context of the coroutine this element.
63-
* The result of this function is the old value of the thread-local state that will be passed to [restoreThreadContext].
64-
* This method should handle its own exceptions and do not rethrow it. Thrown exceptions will leave coroutine which
65-
* context is updated in an undefined state and may crash an application.
66-
*
67-
* @param context the coroutine context.
68-
*/
69-
public fun updateThreadContext(context: CoroutineContext): S
70-
71-
/**
72-
* Restores context of the current thread.
73-
* This function is invoked after the coroutine in the specified [context] is suspended in the current thread
74-
* if [updateThreadContext] was previously invoked on resume of this coroutine.
75-
* The value of [oldState] is the result of the previous invocation of [updateThreadContext] and it should
76-
* be restored in the thread-local state by this function.
77-
* This method should handle its own exceptions and do not rethrow it. Thrown exceptions will leave coroutine which
78-
* context is updated in an undefined state and may crash an application.
79-
*
80-
* @param context the coroutine context.
81-
* @param oldState the value returned by the previous invocation of [updateThreadContext].
82-
*/
83-
public fun restoreThreadContext(context: CoroutineContext, oldState: S)
84-
}
85-
866
/**
877
* A [ThreadContextElement] copied whenever a child coroutine inherits a context containing it.
888
*

kotlinx-coroutines-core/jvm/src/internal/ThreadContext.kt

Lines changed: 0 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -3,57 +3,6 @@ package kotlinx.coroutines.internal
33
import kotlinx.coroutines.*
44
import kotlin.coroutines.*
55

6-
@JvmField
7-
internal val NO_THREAD_ELEMENTS = Symbol("NO_THREAD_ELEMENTS")
8-
9-
// Used when there are >= 2 active elements in the context
10-
@Suppress("UNCHECKED_CAST")
11-
private class ThreadState(@JvmField val context: CoroutineContext, n: Int) {
12-
private val values = arrayOfNulls<Any>(n)
13-
private val elements = arrayOfNulls<ThreadContextElement<Any?>>(n)
14-
private var i = 0
15-
16-
fun append(element: ThreadContextElement<*>, value: Any?) {
17-
values[i] = value
18-
elements[i++] = element as ThreadContextElement<Any?>
19-
}
20-
21-
fun restore(context: CoroutineContext) {
22-
for (i in elements.indices.reversed()) {
23-
elements[i]!!.restoreThreadContext(context, values[i])
24-
}
25-
}
26-
}
27-
28-
// Counts ThreadContextElements in the context
29-
// Any? here is Int | ThreadContextElement (when count is one)
30-
private val countAll =
31-
fun (countOrElement: Any?, element: CoroutineContext.Element): Any? {
32-
if (element is ThreadContextElement<*>) {
33-
val inCount = countOrElement as? Int ?: 1
34-
return if (inCount == 0) element else inCount + 1
35-
}
36-
return countOrElement
37-
}
38-
39-
// Find one (first) ThreadContextElement in the context, it is used when we know there is exactly one
40-
private val findOne =
41-
fun (found: ThreadContextElement<*>?, element: CoroutineContext.Element): ThreadContextElement<*>? {
42-
if (found != null) return found
43-
return element as? ThreadContextElement<*>
44-
}
45-
46-
// Updates state for ThreadContextElements in the context using the given ThreadState
47-
private val updateState =
48-
fun (state: ThreadState, element: CoroutineContext.Element): ThreadState {
49-
if (element is ThreadContextElement<*>) {
50-
state.append(element, element.updateThreadContext(state.context))
51-
}
52-
return state
53-
}
54-
55-
internal actual fun threadContextElements(context: CoroutineContext): Any = context.fold(0, countAll)!!
56-
576
// countOrElement is pre-cached in dispatched continuation
587
// returns NO_THREAD_ELEMENTS if the contest does not have any ThreadContextElements
598
internal fun updateThreadContext(context: CoroutineContext, countOrElement: Any?): Any? {

0 commit comments

Comments
 (0)