CopyableThreadContextElement

A ThreadContextElement copied whenever a child coroutine inherits a context containing it.

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 will be visible to itself and any child coroutine launched after that write.

Writes will not be visible to the parent coroutine, peer coroutines, or coroutines that happen to use the same thread. Writes made to the ThreadLocal by the parent coroutine after launching a child coroutine will not be visible to that child coroutine.

This can be used to allow a coroutine to use a mutable ThreadLocal API transparently and correctly, regardless of the coroutine's structured concurrency.

This example adapts a ThreadLocal method trace to be "coroutine local" while the method trace is in a coroutine:

class TraceContextElement(private val traceData: TraceData?) : CopyableThreadContextElement<TraceData?> {
companion object Key : CoroutineContext.Key<TraceContextElement>

override val key: CoroutineContext.Key<TraceContextElement> = Key

override fun updateThreadContext(context: CoroutineContext): TraceData? {
val oldState = traceThreadLocal.get()
traceThreadLocal.set(traceData)
return oldState
}

override fun restoreThreadContext(context: CoroutineContext, oldState: TraceData?) {
traceThreadLocal.set(oldState)
}

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 mergeForChild(overwritingElement: CoroutineContext.Element): CoroutineContext {
// 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.
// If merging does not require special behavior,
// the copy of the element can be returned.
return TraceContextElement(traceThreadLocal.get()?.copy())
}
}

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.

Reentrancy and thread-safety

Correct implementations of this interface must expect that calls to restoreThreadContext may happen in parallel to the subsequent updateThreadContext and restoreThreadContext operations.

Even though an element is copied for each child coroutine, an implementation should be able to handle the following interleaving when a coroutine with the corresponding element is launched on a multithreaded dispatcher:

coroutine.updateThreadContext() // Thread #1
... coroutine body ...
// suspension + immediate dispatch happen here
coroutine.updateThreadContext() // Thread #2, coroutine is already resumed
// ... coroutine body after suspension point on Thread #2 ...
coroutine.restoreThreadContext() // Thread #1, is invoked late because Thread #1 is slow
coroutine.restoreThreadContext() // Thread #2, may happen in parallel with the previous restore

All implementations of CopyableThreadContextElement should be thread-safe and guard their internal mutable state within an element accordingly.

Functions

Link copied to clipboard

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.

Link copied to clipboard
abstract fun mergeForChild(overwritingElement: CoroutineContext.Element): CoroutineContext

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.