Skip to content

Commit 09a2663

Browse files
committed
PR #7 Move ThreadContextElement to common
1 parent 3a8e3d8 commit 09a2663

File tree

9 files changed

+494
-144
lines changed

9 files changed

+494
-144
lines changed

IntelliJ-patches.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,8 @@ Why not just let `next` return a maybe wrapped value? That's because it is heavi
9090
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.
9191

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

6-
/**
7-
* Defines elements in [CoroutineContext] that are installed into 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 on
11-
* resume of a coroutine and restore later on suspend. 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-
}
84-
856
/**
867
* A [ThreadContextElement] copied whenever a child coroutine inherits a context containing it.
878
*

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)