diff --git a/core/kotlinx-coroutines-core/src/ThreadContextElement.kt b/core/kotlinx-coroutines-core/src/ThreadContextElement.kt index 3e4b4aa5ea..b43497d38d 100644 --- a/core/kotlinx-coroutines-core/src/ThreadContextElement.kt +++ b/core/kotlinx-coroutines-core/src/ThreadContextElement.kt @@ -12,7 +12,7 @@ import kotlin.coroutines.experimental.* * every time the coroutine with this element in the context is resumed on a thread. * * Implementations of this interface define a type [S] of the thread-local state that they need to store on - * resume of a coroutine and restore later on suspend and the infrastructure provides the corresponding storage. + * resume of a coroutine and restore later on suspend. The infrastructure provides the corresponding storage. * * Example usage looks like this: * @@ -42,9 +42,12 @@ import kotlin.coroutines.experimental.* * // Usage * launch(UI + CoroutineName("Progress bar coroutine")) { ... } * ``` - * Every time launched coroutine is executed, UI thread name will be updated to "UI thread original name # Progress bar coroutine" * - * Note that for raw [ThreadLocal]s [asContextElement] factory should be used without any intermediate [ThreadContextElement] implementations + * Every time this coroutine is resumed on a thread, UI thread name is updated to + * "UI thread original name # Progress bar coroutine" and the thread name is restored to the original one when + * this coroutine suspends. + * + * To use [ThreadLocal] variable within the coroutine use [ThreadLocal.asContextElement][asContextElement] function. */ public interface ThreadContextElement : CoroutineContext.Element { /** @@ -71,39 +74,43 @@ public interface ThreadContextElement : CoroutineContext.Element { } /** - * Wraps [ThreadLocal] into [ThreadContextElement]. Resulting [ThreadContextElement] will - * maintain given [ThreadLocal] value for coroutine not depending on actual thread it's run on. - * By default [ThreadLocal.get] is used as a initial value for the element, but it can be overridden with [initialValue] parameter. + * Wraps [ThreadLocal] into [ThreadContextElement]. The resulting [ThreadContextElement] + * maintains the given [value] of the given [ThreadLocal] for coroutine regardless of the actual thread its is resumed on. + * By default [ThreadLocal.get] is used as a value for the thread-local variable, but it can be overridden with [value] parameter. * * Example usage looks like this: + * * ``` * val myThreadLocal = ThreadLocal() * ... - * println(myThreadLocal.get()) // Will print "null" + * println(myThreadLocal.get()) // Prints "null" * launch(CommonPool + myThreadLocal.asContextElement(initialValue = "foo")) { - * println(myThreadLocal.get()) // Will print "foo" + * println(myThreadLocal.get()) // Prints "foo" * withContext(UI) { - * println(myThreadLocal.get()) // Will print "foo", but it's UI thread + * println(myThreadLocal.get()) // Prints "foo", but it's on UI thread * } * } - * - * println(myThreadLocal.get()) // Will print "null" + * println(myThreadLocal.get()) // Prints "null" * ``` * - * Note that context element doesn't track modifications of thread local, for example + * Note that the context element does not track modifications of the thread-local variable, for example: * * ``` * myThreadLocal.set("main") * withContext(UI) { - * println(myThreadLocal.get()) // will print "main" + * println(myThreadLocal.get()) // Prints "main" * myThreadLocal.set("UI") * } - * - * println(myThreadLocal.get()) // will print "main", not "UI" + * println(myThreadLocal.get()) // Prints "main", not "UI" * ``` * - * For modifications mutable boxes should be used instead + * Use `withContext` to update the corresponding thread-local variable to a different value, for example: + * + * ``` + * withContext(myThreadLocal.asContextElement("foo")) { + * println(myThreadLocal.get()) // Prints "foo" + * } + * ``` */ -public fun ThreadLocal.asContextElement(initialValue: T = get()): ThreadContextElement { - return ThreadLocalElement(initialValue, this) -} +public fun ThreadLocal.asContextElement(value: T = get()): ThreadContextElement = + ThreadLocalElement(value, this) diff --git a/core/kotlinx-coroutines-core/src/internal/ThreadContext.kt b/core/kotlinx-coroutines-core/src/internal/ThreadContext.kt index 6c03dbe6a6..9d9ea2ae07 100644 --- a/core/kotlinx-coroutines-core/src/internal/ThreadContext.kt +++ b/core/kotlinx-coroutines-core/src/internal/ThreadContext.kt @@ -89,14 +89,18 @@ internal fun restoreThreadContext(context: CoroutineContext, oldState: Any?) { } } -internal class ThreadLocalElement(private val data: T, private val threadLocal: ThreadLocal) : ThreadContextElement { - private data class Key(private val tl: ThreadLocal<*>) : CoroutineContext.Key> +// top-level data class for a nicer out-of-the-box toString representation and class name +private data class ThreadLocalKey(private val threadLocal: ThreadLocal<*>) : CoroutineContext.Key> - override val key: CoroutineContext.Key<*> = Key(threadLocal) +internal class ThreadLocalElement( + private val value: T, + private val threadLocal: ThreadLocal +) : ThreadContextElement { + override val key: CoroutineContext.Key<*> = ThreadLocalKey(threadLocal) override fun updateThreadContext(context: CoroutineContext): T { val oldState = threadLocal.get() - threadLocal.set(data) + threadLocal.set(value) return oldState } @@ -104,13 +108,15 @@ internal class ThreadLocalElement(private val data: T, private val threadLoca threadLocal.set(oldState) } + // this method is overridden to perform reference comparison on key override fun minusKey(key: CoroutineContext.Key<*>): CoroutineContext { return if (this.key == key) EmptyCoroutineContext else this } + // this method is overridden to perform reference comparison on key public override operator fun get(key: CoroutineContext.Key): E? = @Suppress("UNCHECKED_CAST") if (this.key == key) this as E else null - override fun toString(): String = "ThreadLocal(value=$data, threadLocal = $threadLocal)" + override fun toString(): String = "ThreadLocal(value=$value, threadLocal = $threadLocal)" } diff --git a/core/kotlinx-coroutines-core/test/ThreadLocalTest.kt b/core/kotlinx-coroutines-core/test/ThreadLocalTest.kt index 6476b814ac..b932e75456 100644 --- a/core/kotlinx-coroutines-core/test/ThreadLocalTest.kt +++ b/core/kotlinx-coroutines-core/test/ThreadLocalTest.kt @@ -8,7 +8,6 @@ import kotlin.test.* @Suppress("RedundantAsync") class ThreadLocalTest : TestBase() { - private val stringThreadLocal = ThreadLocal() private val intThreadLocal = ThreadLocal() private val executor = newFixedThreadPoolContext(1, "threadLocalTest") @@ -55,7 +54,7 @@ class ThreadLocalTest : TestBase() { intThreadLocal.set(314) val deferred = async(CommonPool - + intThreadLocal.asContextElement(initialValue = 239) + stringThreadLocal.asContextElement(initialValue = "pew")) { + + intThreadLocal.asContextElement(value = 239) + stringThreadLocal.asContextElement(value = "pew")) { assertEquals(239, intThreadLocal.get()) assertEquals("pew", stringThreadLocal.get()) diff --git a/core/kotlinx-coroutines-core/test/guide/example-context-11.kt b/core/kotlinx-coroutines-core/test/guide/example-context-11.kt index 329afbffc3..4d43911446 100644 --- a/core/kotlinx-coroutines-core/test/guide/example-context-11.kt +++ b/core/kotlinx-coroutines-core/test/guide/example-context-11.kt @@ -8,18 +8,16 @@ package kotlinx.coroutines.experimental.guide.context11 import kotlinx.coroutines.experimental.* import kotlin.coroutines.experimental.* -val threadLocal = ThreadLocal() +val threadLocal = ThreadLocal() // declare thread-local variable fun main(args: Array) = runBlocking { threadLocal.set("main") println("Pre-main, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'") - - val job = launch(CommonPool + threadLocal.asContextElement(initialValue = "launch"), start = CoroutineStart.UNDISPATCHED) { + val job = launch(CommonPool + threadLocal.asContextElement(value = "launch"), start = CoroutineStart.UNDISPATCHED) { println("Launch start, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'") yield() println("After yield, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'") } - job.join() println("Post-main, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'") } diff --git a/coroutines-guide.md b/coroutines-guide.md index cf4b59a77a..5192243aa2 100644 --- a/coroutines-guide.md +++ b/coroutines-guide.md @@ -67,7 +67,7 @@ You need to add a dependency on `kotlinx-coroutines-core` module as explained * [Parental responsibilities](#parental-responsibilities) * [Naming coroutines for debugging](#naming-coroutines-for-debugging) * [Cancellation via explicit job](#cancellation-via-explicit-job) -* [Thread-local data](#thread-local-data) + * [Thread-local data](#thread-local-data) * [Channels](#channels) * [Channel basics](#channel-basics) * [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 since it is synchronous, but this joining ability is useful when building backend services to ensure bounded resource usage. -## Thread-local data -Sometimes it's very convenient to have an ability to pass some thread-local data, but for coroutines, which -are not bound to any particular thread, it's hard to achieve it manually without writing a lot of boilerplate. +### Thread-local data + +Sometimes it is very convenient to have an ability to pass some thread-local data, but, for coroutines, which +are not bound to any particular thread, it is hard to achieve it manually without writing a lot of boilerplate. For [`ThreadLocal`](https://docs.oracle.com/javase/8/docs/api/java/lang/ThreadLocal.html), -[asContextElement] is here for the rescue. It creates is an additional context element, -which remembers given `ThreadLocal` and restores it every time coroutine switches its context. +[asContextElement] is here for the rescue. It creates an additional context element, +which keep the value of the given `ThreadLocal` and restores it every time the coroutine switches its context. -It's easy to demonstrate it in action: +It is easy to demonstrate it in action: + ```kotlin -val threadLocal = ThreadLocal() +val threadLocal = ThreadLocal() // declare thread-local variable fun main(args: Array) = runBlocking { threadLocal.set("main") println("Pre-main, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'") - - val job = launch(CommonPool + threadLocal.asContextElement(initialValue = "launch"), start = CoroutineStart.UNDISPATCHED) { + val job = launch(CommonPool + threadLocal.asContextElement(value = "launch"), start = CoroutineStart.UNDISPATCHED) { println("Launch start, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'") yield() println("After yield, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'") } - job.join() println("Post-main, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'") } -``` +``` > You can get full code [here](core/kotlinx-coroutines-core/test/guide/example-context-11.kt) @@ -1304,17 +1304,21 @@ After yield, current thread: Thread[ForkJoinPool.commonPool-worker-1 @coroutine# Post-main, current thread: Thread[main @coroutine#1,5,main], thread local value: 'main' ``` + -Note that thread-local is restored properly, no matter on what thread coroutine is executed. +Note how thread-local value is restored properly, no matter on what thread the coroutine is executed. `ThreadLocal` has first-class support and can be used with any primitive `kotlinx.corotuines` provides. -It has the only limitation: if thread-local is mutated, a new value is not propagated to the coroutine caller -(as context element cannot track all `ThreadLocal` object accesses), but is properly propagated to newly launched coroutines. -To workaround it, value can be stored in a mutable box like `class Counter(var i: Int)` +It has one key limitation: when thread-local is mutated, a new value is not propagated to the coroutine caller +(as context element cannot track all `ThreadLocal` object accesses) and updated value is lost on the next suspension. +Use [withContext] to update the value of the thread-local in a coroutine, see [asContextElement] for more details. -For advanced usage, for example for integration with logging MDC, transactional contexts or any other libraries -which use thread-locals for passing data, [ThreadContextElement] interface should be implemented. +Alternatively, a value can be stored in a mutable box like `class Counter(var i: Int)`, which is, in turn, +is stored in a thread-local variable. However, in this case you are fully responsible to synchronize +potentially concurrent modifications to the variable in this box. - +For advanced usage, for example for integration with logging MDC, transactional contexts or any other libraries +which internally use thread-locals for passing data, see documentation for [ThreadContextElement] interface +that should be implemented. ## Channels