Skip to content

Commit a86e186

Browse files
committed
ThreadLocalElement: stylistic updates to guide and docs
1 parent 91e7420 commit a86e186

File tree

5 files changed

+63
-49
lines changed

5 files changed

+63
-49
lines changed

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

+26-19
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import kotlin.coroutines.experimental.*
1212
* every time the coroutine with this element in the context is resumed on a thread.
1313
*
1414
* Implementations of this interface define a type [S] of the thread-local state that they need to store on
15-
* resume of a coroutine and restore later on suspend and the infrastructure provides the corresponding storage.
15+
* resume of a coroutine and restore later on suspend. The infrastructure provides the corresponding storage.
1616
*
1717
* Example usage looks like this:
1818
*
@@ -42,9 +42,12 @@ import kotlin.coroutines.experimental.*
4242
* // Usage
4343
* launch(UI + CoroutineName("Progress bar coroutine")) { ... }
4444
* ```
45-
* Every time launched coroutine is executed, UI thread name will be updated to "UI thread original name # Progress bar coroutine"
4645
*
47-
* Note that for raw [ThreadLocal]s [asContextElement] factory should be used without any intermediate [ThreadContextElement] implementations
46+
* Every time this coroutine is resumed on a thread, UI thread name is updated to
47+
* "UI thread original name # Progress bar coroutine" and the thread name is restored to the original one when
48+
* this coroutine suspends.
49+
*
50+
* To use [ThreadLocal] variable within the coroutine use [ThreadLocal.asContextElement][asContextElement] function.
4851
*/
4952
public interface ThreadContextElement<S> : CoroutineContext.Element {
5053
/**
@@ -71,39 +74,43 @@ public interface ThreadContextElement<S> : CoroutineContext.Element {
7174
}
7275

7376
/**
74-
* Wraps [ThreadLocal] into [ThreadContextElement]. Resulting [ThreadContextElement] will
75-
* maintain given [ThreadLocal] value for coroutine not depending on actual thread it's run on.
76-
* By default [ThreadLocal.get] is used as a initial value for the element, but it can be overridden with [initialValue] parameter.
77+
* Wraps [ThreadLocal] into [ThreadContextElement]. The resulting [ThreadContextElement]
78+
* maintains the given [value] of the given [ThreadLocal] for coroutine regardless of the actual thread its is resumed on.
79+
* By default [ThreadLocal.get] is used as a value for the thread-local variable, but it can be overridden with [value] parameter.
7780
*
7881
* Example usage looks like this:
82+
*
7983
* ```
8084
* val myThreadLocal = ThreadLocal<String?>()
8185
* ...
82-
* println(myThreadLocal.get()) // Will print "null"
86+
* println(myThreadLocal.get()) // Prints "null"
8387
* launch(CommonPool + myThreadLocal.asContextElement(initialValue = "foo")) {
84-
* println(myThreadLocal.get()) // Will print "foo"
88+
* println(myThreadLocal.get()) // Prints "foo"
8589
* withContext(UI) {
86-
* println(myThreadLocal.get()) // Will print "foo", but it's UI thread
90+
* println(myThreadLocal.get()) // Prints "foo", but it's on UI thread
8791
* }
8892
* }
89-
*
90-
* println(myThreadLocal.get()) // Will print "null"
93+
* println(myThreadLocal.get()) // Prints "null"
9194
* ```
9295
*
93-
* Note that context element doesn't track modifications of thread local, for example
96+
* Note that the context element does not track modifications of the thread-local variable, for example:
9497
*
9598
* ```
9699
* myThreadLocal.set("main")
97100
* withContext(UI) {
98-
* println(myThreadLocal.get()) // will print "main"
101+
* println(myThreadLocal.get()) // Prints "main"
99102
* myThreadLocal.set("UI")
100103
* }
101-
*
102-
* println(myThreadLocal.get()) // will print "main", not "UI"
104+
* println(myThreadLocal.get()) // Prints "main", not "UI"
103105
* ```
104106
*
105-
* For modifications mutable boxes should be used instead
107+
* Use `withContext` to update the corresponding thread-local variable to a different value, for example:
108+
*
109+
* ```
110+
* withContext(myThreadLocal.asContextElement("foo")) {
111+
* println(myThreadLocal.get()) // Prints "foo"
112+
* }
113+
* ```
106114
*/
107-
public fun <T> ThreadLocal<T>.asContextElement(initialValue: T = get()): ThreadContextElement<T> {
108-
return ThreadLocalElement(initialValue, this)
109-
}
115+
public fun <T> ThreadLocal<T>.asContextElement(value: T = get()): ThreadContextElement<T> =
116+
ThreadLocalElement(value, this)

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

+11-5
Original file line numberDiff line numberDiff line change
@@ -89,28 +89,34 @@ internal fun restoreThreadContext(context: CoroutineContext, oldState: Any?) {
8989
}
9090
}
9191

92-
internal class ThreadLocalElement<T>(private val data: T, private val threadLocal: ThreadLocal<T>) : ThreadContextElement<T> {
93-
private data class Key(private val tl: ThreadLocal<*>) : CoroutineContext.Key<ThreadLocalElement<*>>
92+
// top-level data class for a nicer out-of-the-box toString representation and class name
93+
private data class ThreadLocalKey(private val threadLocal: ThreadLocal<*>) : CoroutineContext.Key<ThreadLocalElement<*>>
9494

95-
override val key: CoroutineContext.Key<*> = Key(threadLocal)
95+
internal class ThreadLocalElement<T>(
96+
private val value: T,
97+
private val threadLocal: ThreadLocal<T>
98+
) : ThreadContextElement<T> {
99+
override val key: CoroutineContext.Key<*> = ThreadLocalKey(threadLocal)
96100

97101
override fun updateThreadContext(context: CoroutineContext): T {
98102
val oldState = threadLocal.get()
99-
threadLocal.set(data)
103+
threadLocal.set(value)
100104
return oldState
101105
}
102106

103107
override fun restoreThreadContext(context: CoroutineContext, oldState: T) {
104108
threadLocal.set(oldState)
105109
}
106110

111+
// this method is overridden to perform reference comparison on key
107112
override fun minusKey(key: CoroutineContext.Key<*>): CoroutineContext {
108113
return if (this.key == key) EmptyCoroutineContext else this
109114
}
110115

116+
// this method is overridden to perform reference comparison on key
111117
public override operator fun <E : CoroutineContext.Element> get(key: CoroutineContext.Key<E>): E? =
112118
@Suppress("UNCHECKED_CAST")
113119
if (this.key == key) this as E else null
114120

115-
override fun toString(): String = "ThreadLocal(value=$data, threadLocal = $threadLocal)"
121+
override fun toString(): String = "ThreadLocal(value=$value, threadLocal = $threadLocal)"
116122
}

core/kotlinx-coroutines-core/test/ThreadLocalTest.kt

+1-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import kotlin.test.*
88

99
@Suppress("RedundantAsync")
1010
class ThreadLocalTest : TestBase() {
11-
1211
private val stringThreadLocal = ThreadLocal<String?>()
1312
private val intThreadLocal = ThreadLocal<Int?>()
1413
private val executor = newFixedThreadPoolContext(1, "threadLocalTest")
@@ -55,7 +54,7 @@ class ThreadLocalTest : TestBase() {
5554
intThreadLocal.set(314)
5655

5756
val deferred = async(CommonPool
58-
+ intThreadLocal.asContextElement(initialValue = 239) + stringThreadLocal.asContextElement(initialValue = "pew")) {
57+
+ intThreadLocal.asContextElement(value = 239) + stringThreadLocal.asContextElement(value = "pew")) {
5958
assertEquals(239, intThreadLocal.get())
6059
assertEquals("pew", stringThreadLocal.get())
6160

core/kotlinx-coroutines-core/test/guide/example-context-11.kt

+2-4
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,16 @@ package kotlinx.coroutines.experimental.guide.context11
88
import kotlinx.coroutines.experimental.*
99
import kotlin.coroutines.experimental.*
1010

11-
val threadLocal = ThreadLocal<String?>()
11+
val threadLocal = ThreadLocal<String?>() // declare thread-local variable
1212

1313
fun main(args: Array<String>) = runBlocking<Unit> {
1414
threadLocal.set("main")
1515
println("Pre-main, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'")
16-
17-
val job = launch(CommonPool + threadLocal.asContextElement(initialValue = "launch"), start = CoroutineStart.UNDISPATCHED) {
16+
val job = launch(CommonPool + threadLocal.asContextElement(value = "launch"), start = CoroutineStart.UNDISPATCHED) {
1817
println("Launch start, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'")
1918
yield()
2019
println("After yield, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'")
2120
}
22-
2321
job.join()
2422
println("Post-main, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'")
2523
}

coroutines-guide.md

+23-19
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ You need to add a dependency on `kotlinx-coroutines-core` module as explained
6767
* [Parental responsibilities](#parental-responsibilities)
6868
* [Naming coroutines for debugging](#naming-coroutines-for-debugging)
6969
* [Cancellation via explicit job](#cancellation-via-explicit-job)
70-
* [Thread-local data](#thread-local-data)
70+
* [Thread-local data](#thread-local-data)
7171
* [Channels](#channels)
7272
* [Channel basics](#channel-basics)
7373
* [Closing and iteration over channels](#closing-and-iteration-over-channels)
@@ -1262,36 +1262,36 @@ and cancel it when activity is destroyed. We cannot `join` them in the case of A
12621262
since it is synchronous, but this joining ability is useful when building backend services to ensure bounded
12631263
resource usage.
12641264

1265-
## Thread-local data
1266-
Sometimes it's very convenient to have an ability to pass some thread-local data, but for coroutines, which
1267-
are not bound to any particular thread, it's hard to achieve it manually without writing a lot of boilerplate.
1265+
### Thread-local data
1266+
1267+
Sometimes it is very convenient to have an ability to pass some thread-local data, but, for coroutines, which
1268+
are not bound to any particular thread, it is hard to achieve it manually without writing a lot of boilerplate.
12681269

12691270
For [`ThreadLocal`](https://docs.oracle.com/javase/8/docs/api/java/lang/ThreadLocal.html),
1270-
[asContextElement] is here for the rescue. It creates is an additional context element,
1271-
which remembers given `ThreadLocal` and restores it every time coroutine switches its context.
1271+
[asContextElement] is here for the rescue. It creates an additional context element,
1272+
which keep the value of the given `ThreadLocal` and restores it every time the coroutine switches its context.
12721273

1273-
It's easy to demonstrate it in action:
1274+
It is easy to demonstrate it in action:
12741275

12751276
<!--- INCLUDE
12761277
import kotlin.coroutines.experimental.*
12771278
-->
1279+
12781280
```kotlin
1279-
val threadLocal = ThreadLocal<String?>()
1281+
val threadLocal = ThreadLocal<String?>() // declare thread-local variable
12801282

12811283
fun main(args: Array<String>) = runBlocking<Unit> {
12821284
threadLocal.set("main")
12831285
println("Pre-main, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'")
1284-
1285-
val job = launch(CommonPool + threadLocal.asContextElement(initialValue = "launch"), start = CoroutineStart.UNDISPATCHED) {
1286+
val job = launch(CommonPool + threadLocal.asContextElement(value = "launch"), start = CoroutineStart.UNDISPATCHED) {
12861287
println("Launch start, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'")
12871288
yield()
12881289
println("After yield, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'")
12891290
}
1290-
12911291
job.join()
12921292
println("Post-main, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'")
12931293
}
1294-
```
1294+
```
12951295

12961296
> You can get full code [here](core/kotlinx-coroutines-core/test/guide/example-context-11.kt)
12971297
@@ -1304,17 +1304,21 @@ After yield, current thread: Thread[ForkJoinPool.commonPool-worker-1 @coroutine#
13041304
Post-main, current thread: Thread[main @coroutine#1,5,main], thread local value: 'main'
13051305
```
13061306

1307+
<!--- TEST FLEXIBLE_THREAD -->
13071308

1308-
Note that thread-local is restored properly, no matter on what thread coroutine is executed.
1309+
Note how thread-local value is restored properly, no matter on what thread the coroutine is executed.
13091310
`ThreadLocal` has first-class support and can be used with any primitive `kotlinx.corotuines` provides.
1310-
It has the only limitation: if thread-local is mutated, a new value is not propagated to the coroutine caller
1311-
(as context element cannot track all `ThreadLocal` object accesses), but is properly propagated to newly launched coroutines.
1312-
To workaround it, value can be stored in a mutable box like `class Counter(var i: Int)`
1311+
It has one key limitation: when thread-local is mutated, a new value is not propagated to the coroutine caller
1312+
(as context element cannot track all `ThreadLocal` object accesses) and updated value is lost on the next suspension.
1313+
Use [withContext] to update the value of the thread-local in a coroutine, see [asContextElement] for more details.
13131314

1314-
For advanced usage, for example for integration with logging MDC, transactional contexts or any other libraries
1315-
which use thread-locals for passing data, [ThreadContextElement] interface should be implemented.
1315+
Alternatively, a value can be stored in a mutable box like `class Counter(var i: Int)`, which is, in turn,
1316+
is stored in a thread-local variable. However, in this case you are fully responsible to synchronize
1317+
potentially concurrent modifications to the variable in this box.
13161318

1317-
<!--- TEST FLEXIBLE_THREAD -->
1319+
For advanced usage, for example for integration with logging MDC, transactional contexts or any other libraries
1320+
which internally use thread-locals for passing data, see documentation for [ThreadContextElement] interface
1321+
that should be implemented.
13181322

13191323
## Channels
13201324

0 commit comments

Comments
 (0)