MDCContext

MDCContext

class MDCContext(    val contextMap: MDCContextMap = MDC.getCopyOfContextMap()) : AbstractCoroutineContextElement, ThreadContextElement<MDCContextMap> (source)

MDC context element for CoroutineContext.

Example:

MDC.put("kotlin", "rocks") // Put a value into the MDC context

launch(MDCContext()) {
    logger.info { "..." }   // The MDC context contains the mapping here
}

Note that you cannot update MDC context from inside the coroutine simply using MDC.put. These updates are going to be lost on the next suspension and reinstalled to the MDC context that was captured or explicitly specified in contextMap when this object was created on the next resumption.

For example, the following code will not work as expected:

launch(MDCContext()) {
    MDC.put("key", "value") // This update will be lost
    delay(100)
    println(MDC.get("key")) // This will print null
}

Instead, you should use withContext to capture the updated MDC context:

launch(MDCContext()) {
    MDC.put("key", "value") // This update will be captured
    withContext(MDCContext()) {
        delay(100)
        println(MDC.get("key")) // This will print "value"
    }
}

There is no way to implicitly propagate MDC context updates from inside the coroutine to the outer scope. You have to capture the updated MDC context and restore it explicitly. For example:

MDC.put("a", "b")
val contextMap = withContext(MDCContext()) {
    MDC.put("key", "value")
    withContext(MDCContext()) {
        MDC.put("key2", "value2")
        withContext(MDCContext()) {
            yield()
            MDC.getCopyOfContextMap()
        }
    }
}
// contextMap contains: {"a"="b", "key"="value", "key2"="value2"}
MDC.setContextMap(contextMap)

https://stackoverflow.com/questions/46227462/how-to-use-code-that-relies-on-threadlocal-with-kotlin-coroutines

Some JVM frameworks use ThreadLocal to store the call context of a application, like the SLF4j MDC,

However, Kotlin coroutines are dispatched on different threads, so how it can be made to work?


 

Answer A

Coroutine's analog to ThreadLocal is CoroutineContext.

Here is an example. Let us assume that we use some framework that relies on a specific ThreadLocal to store some application-specific data (MyData in this example):

val myThreadLocal = ThreadLocal<MyData>()

 

To use it with coroutines, you'll need to implement a context that keeps the current value of MyData and puts it into the corresponding ThreadLocal every time the coroutine is resumed on a thread. The code should look like this:

class MyContext(
    private var myData: MyData,
    private val dispatcher: ContinuationInterceptor
) : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
    override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
        dispatcher.interceptContinuation(Wrapper(continuation))

    inner class Wrapper<T>(private val continuation: Continuation<T>): Continuation<T> {
        private inline fun wrap(block: () -> Unit) {
            try {
                myThreadLocal.set(myData)
                block()
            } finally {
                myData = myThreadLocal.get()
            }
        }

        override val context: CoroutineContext get() = continuation.context
        override fun resume(value: T) = wrap { continuation.resume(value) }
        override fun resumeWithException(exception: Throwable) = wrap { continuation.resumeWithException(exception) }
    }
}

 

To use it in your coroutines, you wrap the dispatcher that you want to use with MyContext and give it the initial value of your data. This value will be put into the thread-local on the thread where the coroutine is resumed.

launch(MyContext(MyData(), CommonPool)) {
    // do something...
}

Answer B

Though this question is quite an old one, but I would want to add to Roman's answer another possible approach with CopyableThreadContextElement. Maybe it will be helpful for somebody else.

// Snippet from the source code's comment
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())
    }
}