How to use code that relies on ThreadLocal with Kotlin coroutines

In some JVM infrastructures, ThreadLocal used to store the application call context, for example, SLF4j MDC , transaction managers, security managers, and others.

However, Kotlin escorts go on different topics, so how can they be made to work?

(The question is based on the GitHub issue )

+7
coroutine kotlin kotlinx.coroutines
source share
1 answer

Coroutine analog to ThreadLocal CoroutineContext .

To interact with ThreadLocal using libraries, you need to implement a custom ContinuationInterceptor that supports infrastructure-related stream locators.

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

 val myThreadLocal = ThreadLocal<MyData>() 

To use it with coroutines, you need to implement a context that stores the current value of MyData and puts it in the corresponding ThreadLocal every time the coroutine resumes in the 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 end the dispatcher that you want to use with MyContext and give it the initial value of your data. This value will be placed in the stream-local in the stream where the coroutine is escorted.

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

The above implementation will also track any changes in the local stream that have been made and save them in this context, so that a multiple call can share the "local-stream" data through the context.

+8
source share

All Articles