How to use code that relies on ThreadLocal with Kotlin coroutines - kotlin

Some JVM frameworks use ThreadLocal to store the call context of a application, like the SLF4j MDC, transaction managers, security managers, and others.
However, Kotlin coroutines are dispatched on different threads, so how it can be made to work?
(The question is inspired by GitHub issue)

Coroutine's analog to ThreadLocal is CoroutineContext.
To interoperate with ThreadLocal-using libraries you need to implement a custom ContinuationInterceptor that supports framework-specific thread-locals.
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...
}
The implementation above would also track any changes to the thread-local that was done and store it in this context, so this way multiple invocation can share "thread-local" data via context.
UPDATE: Starting with kotlinx.corutines version 0.25.0 there is direct support for representing Java ThreadLocal instances as coroutine context elements. See this documentation for details. There is also out-of-the-box support for SLF4J MDC via kotlinx-coroutines-slf4j integration module.

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())
}
}
Note that copyForChild method allows you to propagate thread local data taken from the parent coroutine's last resumption phase to the local context of the child coroutine (as Copyable in CopyableThreadContextElement implies), because method copyForChild will be invoked on the parent coroutine's thread associated with the corresponding resumption phase when a child coroutine was created.
Just by adding TraceContextElement context element to the root coroutine's context it will be propagated to all child coroutines as context element.
runBlocking(Dispatchers.IO + TraceContextElement(someTraceDataInstance)){...}
Whereas with ContinuationInterceptor approach additional wrapping can be necessary for child coroutines' builders, if you redefine dispatchers for child coroutines.
fun main() {
runBlocking(WrappedDispatcher(Dispatchers.IO)) {
delay(100)
println("It is wrapped!")
delay(100)
println("It is also wrapped!")
// NOTE: we don't wrap with the WrappedDispatcher class here
// redefinition of the dispatcher leads to replacement of our custom ContinuationInterceptor
// with logic taken from specified dispatcher (in the case below from Dispatchers.Default)
withContext(Dispatchers.Default) {
delay(100)
println("It is nested coroutine, and it isn't wrapped!")
delay(100)
println("It is nested coroutine, and it isn't wrapped!")
}
delay(100)
println("It is also wrapped!")
}
}
with wrapper overriding ContinuationInterceptor interface
class WrappedDispatcher(
private val dispatcher: ContinuationInterceptor
) : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
dispatcher.interceptContinuation(ContinuationWrapper(continuation))
private class ContinuationWrapper<T>(val base: Continuation<T>) : Continuation<T> by base {
override fun resumeWith(result: Result<T>) {
println("------WRAPPED START-----")
base.resumeWith(result)
println("------WRAPPED END-------")
}
}
}
output:
------WRAPPED START-----
------WRAPPED END-------
------WRAPPED START-----
It is wrapped!
------WRAPPED END-------
------WRAPPED START-----
It is also wrapped!
------WRAPPED END-------
It is nested coroutine, and it isn't wrapped!
It is nested coroutine, and it isn't wrapped!
------WRAPPED START-----
------WRAPPED END-------
------WRAPPED START-----
It is also wrapped!
------WRAPPED END-------
as you can see for the child (nested) coroutine our wrapper wasn't applied, since we reassigned a ContinuationInterceptor supplying another dispatcher as a parameter. This can lead to a problem as you can mistakenly forget to wrap a child coroutine's dispatcher.
As a side note, if you decide to choose this approach with ContinuationInterceptor, then consider to add such extension
fun ContinuationInterceptor.withMyProjectWrappers() = WrappedDispatcher(this)
wrapping your dispatcher with all necessary wrappers you have in the project, obviously it can be easily extended taking specific beans (wrappers) from an IoC container such as Spring.
And also as an extra example of CopyableThreadContextElement where thread local changes are saved in all resumptions phases.
Executors.newFixedThreadPool(..).asCoroutineDispatcher() is used to
better illustrate that different threads can be working between
resumptions phases.
val counterThreadLocal: ThreadLocal<Int> = ThreadLocal.withInitial{ 1 }
fun showCounter(){
println("-------------------------------------------------")
println("Thread: ${Thread.currentThread().name}\n Counter value: ${counterThreadLocal.get()}")
}
fun main() {
runBlocking(Executors.newFixedThreadPool(10).asCoroutineDispatcher() + CounterPropagator(1)) {
showCounter()
delay(100)
showCounter()
counterThreadLocal.set(2)
delay(100)
showCounter()
counterThreadLocal.set(3)
val nested = async(Executors.newFixedThreadPool(10).asCoroutineDispatcher()) {
println("-----------NESTED START---------")
showCounter()
delay(100)
counterThreadLocal.set(4)
showCounter()
println("------------NESTED END-----------")
}
nested.await()
showCounter()
println("---------------END------------")
}
}
class CounterPropagator(private var counterFromParenCoroutine: Int) : CopyableThreadContextElement<Int> {
companion object Key : CoroutineContext.Key<CounterPropagator>
override val key: CoroutineContext.Key<CounterPropagator> = Key
override fun updateThreadContext(context: CoroutineContext): Int {
// initialize thread local on the resumption
counterThreadLocal.set(counterFromParenCoroutine)
return 0
}
override fun restoreThreadContext(context: CoroutineContext, oldState: Int) {
// propagate thread local changes between resumption phases in the same coroutine
counterFromParenCoroutine = counterThreadLocal.get()
}
override fun copyForChild(): CounterPropagator {
// propagate thread local changes to children
return CounterPropagator(counterThreadLocal.get())
}
override fun mergeForChild(overwritingElement: CoroutineContext.Element): CoroutineContext {
return CounterPropagator(counterThreadLocal.get())
}
}
output:
-------------------------------------------------
Thread: pool-1-thread-1
Counter value: 1
-------------------------------------------------
Thread: pool-1-thread-2
Counter value: 1
-------------------------------------------------
Thread: pool-1-thread-3
Counter value: 2
-----------NESTED START---------
-------------------------------------------------
Thread: pool-2-thread-1
Counter value: 3
-------------------------------------------------
Thread: pool-2-thread-2
Counter value: 4
------------NESTED END-----------
-------------------------------------------------
Thread: pool-1-thread-4
Counter value: 3
---------------END------------
You can achieve similar behavior with ContinuationInterceptor (but don't forget to re-wrap dispatchers of child (nested) coroutines in the coroutine builder as was mentioned above)
val counterThreadLocal: ThreadLocal<Int> = ThreadLocal()
class WrappedDispatcher(
private val dispatcher: ContinuationInterceptor,
private var savedCounter: Int = counterThreadLocal.get() ?: 0
) : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
dispatcher.interceptContinuation(ContinuationWrapper(continuation))
private inner class ContinuationWrapper<T>(val base: Continuation<T>) : Continuation<T> by base {
override fun resumeWith(result: Result<T>) {
counterThreadLocal.set(savedCounter)
try {
base.resumeWith(result)
} finally {
savedCounter = counterThreadLocal.get()
}
}
}
}

Related

Shared lazy coroutine / memoized coroutine execution

Context:
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
data class DatabaseData(
val a: String,
val b: String
)
interface DatabaseFetcher {
suspend fun get(): DatabaseData
}
class MyClass(
private val databaseFetcher: DatabaseFetcher
) {
suspend fun a() = coroutineScope {
val a = async { databaseFetcher.get().a }
//imagine more async{}'s here
a //imagine a gets computed in regards to the other async{}'s as well
}
suspend fun b() = databaseFetcher.get().b
}
class MyController(private val databaseFetcher: DatabaseFetcher) {
suspend fun someFun() = coroutineScope {
// the reduced example doesn't need a coroutineScope of course, imagine some async{} here
MyClass(databaseFetcher)
}
}
I am trying to call databaseFetcher.get() only once if either a() or b() are called on MyClass. Basically a lazy future that gets started when either a() or b() is called, but with coroutines.
What I have tried so far:
Can't use by lazy{} as the coroutineScope matters here and I can't use withContext(Dispatchers.IO) as I use a custom Context (multithreading, Spring request scoped data etc.) - passing my context in here seems awkward (would it be bad practice?)
I can't pass an async(start = CoroutineStart.LAZY) when constructing MyClass as it would block indefinitely if the Deferred<T> is never await()ed on, which may happen when neither a() or b() is called. It also blocks indefinitely because the corresponding coroutineScope is constructed when MyClass is constructed which would block as a() and b() are called later after MyClass has been fully constructed because (as I understand) a coroutineScope is only unblocked when all its children are done, which doesn't hold true for a lazy async thats awaited outside the curent scope
Using a wider coroutine context may leak when the lazy async is never awaited - is this true? I couldn't find much about this
This is being done in the context of GraphQL wherein either a b or both can be selected. There are boilerplatey solutions to this but as I am still learning about coroutines I wondered if there is an elegant solution to this which I don't see yet. The CoroutineStart.LAZY issue really caught me by surprise :)
I have found a solution for this:
fun <T : () -> U, U> T.memoized(): suspend () -> Deferred<U> {
val self = this
val deferred: CompletableDeferred<U> = CompletableDeferred()
val started = AtomicBoolean(false)
return suspend {
if (started.compareAndExchange(false, true)) {
deferred
} else {
coroutineScope {
async {
deferred.complete(self())
deferred.await()
}
}
}
}
}
Any () -> T function (basically any function with captured arguments) can be .memoized(). Whatever callee first calls the returned suspend fun will be used to start the Deferred<U> while allowing said callee to block whenever he sees fit:
val expensive = { someExpensiveFun(param, param2 }.memoize();
withContext(Dispatchers.IO) { // or some other context
val a = expensive()
val b = expensive()
a.await()
b.await()
}

Do I need to warp the collectAsState() of a hot Flow with repeatOnLifecycle in #Composable?

I have read the article A safer way to collect flows from Android UIs.
I know the following content.
A cold flow backed by a channel or using operators with buffers such as buffer, conflate, flowOn, or shareIn is not safe to collect with some of the existing APIs such as CoroutineScope.launch, Flow.launchIn, or LifecycleCoroutineScope.launchWhenX, unless you manually cancel the Job that started the coroutine when the activity goes to the background. These APIs will keep the underlying flow producer active while emitting items into the buffer in the background, and thus wasting resources.
The Code A is from the official sample project.
The viewModel.suggestedDestinations is a MutableStateFlow, it's a hot Flow.
I don't know if the operation collectAsState() of hot Flow is safe in #Composable UI.
1: Do I need to use the Code just like Code B or Code C replace Code A for a hot Flow?
2: Is the operation collectAsState() of cold Flow safe in #Composable UI.
Code A
#OptIn(ExperimentalMaterialApi::class)
#Composable
fun CraneHomeContent(
onExploreItemClicked: OnExploreItemClicked,
openDrawer: () -> Unit,
modifier: Modifier = Modifier,
viewModel: MainViewModel = viewModel(),
) {
val suggestedDestinations by viewModel.suggestedDestinations.collectAsState()
...
}
#HiltViewModel
class MainViewModel #Inject constructor(
...
) : ViewModel() {
...
private val _suggestedDestinations = MutableStateFlow<List<ExploreModel>>(emptyList())
val suggestedDestinations: StateFlow<List<ExploreModel>>
}
Code B
class LocationActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
...
}
}
}
}
Code C
#Composable
fun LocationScreen(locationFlow: Flow<Flow>) {
val lifecycleOwner = LocalLifecycleOwner.current
val locationFlowLifecycleAware = remember(locationFlow, lifecycleOwner) {
locationFlow.flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED)
}
val location by locationFlowLifecycleAware.collectAsState()
...
}
collectAsState (Code A) is safe for any kind of Flow (cold/hot it doesn't matter). If you look at how collectAsState is implemented then you will see that it uses a LaunchedEffect deep down (collectAsState -> produceState -> LaunchedEffect)
internal class LaunchedEffectImpl(
parentCoroutineContext: CoroutineContext,
private val task: suspend CoroutineScope.() -> Unit
) : RememberObserver {
private val scope = CoroutineScope(parentCoroutineContext)
private var job: Job? = null
override fun onRemembered() {
job?.cancel("Old job was still running!")
job = scope.launch(block = task)
}
override fun onForgotten() {
job?.cancel()
job = null
}
override fun onAbandoned() {
job?.cancel()
job = null
}
}
which creates a coroutine scope and launches the task lambda once it enters the composition and cancels it automatically once it leaves the composition.
In Code A, viewModel.suggestedDestinations.collectAsState() (together with it's LaunchedEffect and it's coroutine scope) will be active as long as CraneHomeContent is being called by some other code. As soon as CraneHomeContent is stopped being called the LaunchedEffect inside of collectAsState() is canceled (and coroutine scope as well).
If it's called from multiple places then there will be multiple LaunchedEffects and thus multiple coroutine scopes.

How to call suspend function from Service Android?

How to provide scope or how to call suspend function from Service Android?
Usually, activity or viewmodel provides us the scope, from where we can launch suspend but there is no similar thing in Service
You can create your own CoroutineScope with a SupervisorJob that you can cancel in the onDestroy() method. The coroutines created with this scope will live as long as your Service is being used. Once onDestroy() of your service is called, all coroutines started with this scope will be cancelled.
class YourService : Service() {
private val job = SupervisorJob()
private val scope = CoroutineScope(Dispatchers.IO + job)
...
fun foo() {
scope.launch {
// Call your suspend function
}
}
override fun onDestroy() {
super.onDestroy()
job.cancel()
}
}
Edit: Changed Dispatchers.Main to Dispatchers.IO
SupervisorJob() (Kotlin GitHub) is a job that provides uniderectional cancellation; it allows for cancellations to propogate downwards only.
The SupervisorJob ... is similar to a regular Job with the only exception that cancellation is propagated only downwards. [KotlinLang.org]
Use case: You have a service that makes a log entry, checks settings, and depending on those settings goes ahead and performs some actions. Do you want all children jobs of the parent job (scope) to cancel if, for instance, a job run based on settings' values throws an exception? If not (i.e. you still want your logging and check for settings jobs to complete at the least) then you want to use the SupervisorJob(), or even supervisorScope (Kotlin GitHub) for 'scoped concurrency' [KotlinLang.org], as both provide unidirectional job cancellation - and in that case the provided answer works.
Coroutine Exception Handling - Supervision (KotlinLang.org)
However, there is a more direct solution that answers the question.
To provide to your service a scope with which to run coroutines (or suspending functions) that execute blocking code, you can simply create a new CoroutineScope() with an EmptyCoroutineContext:
(Snippet from CoroutineScope Documentation)
If the given context does not contain a Job element, then a default Job() is created. This way, cancellation or failure of any child coroutine in this scope cancels all the other children, just like inside coroutineScope block [Kotlin GitHub]
class YourClass : Extended() {
...
private val serviceScope: CoroutineScope( EmptyCoroutineContext )
...
private inner class ServiceHandler( looper: Looper ): Handler( looper ) {
override fun handleMessage( msg: Message ) {
super.handleMessage( msg )
serviceScope.launch {
try{
+ ...
} catch( e: Exception ) {
+ ...
} finally {
stopSelf( msg.arg1 )
}
}
}
}
override fun onCreate(){
+ ...
}
override fun onDestroy(){
/* In a service, unlike in an activity, we do not
need to make a call to the super implementation */
//super.onDestory()
serviceScope.cancel()
}
}
for me worked like that
import androidx.lifecycle.lifecycleScope
class ServiceLife : LifecycleService() {
private var supervisorJob = SupervisorJob(parent = null)
override fun onCreate() {
super.onCreate()
val serviceJob = lifecycleScope.launch {
//some suspend fun
}
supervisorJob[serviceJob.key]
supervisorJob.cancel()
}
}

Trigger event listeners async with Kotlin Coroutines

I have created an abstract Event class which is used to create events in Kotlin. Now I would like to use Coroutines to call each subscriber asynchronously.
abstract class Event<T> {
private var handlers = listOf<(T) -> Unit>()
infix fun on(handler: (T) -> Unit) {
handlers += handler
println(handlers.count())
}
fun emit(event: T) =
runBlocking {
handlers.forEach { subscriber ->
GlobalScope.launch {
subscriber(event)
}
}
}
}
And a concrete class that can be used to create event listeners and event publishers
class AsyncEventTest {
companion object : Event<AsyncEventTest>()
fun emit() = emit(this)
}
The issue is that when I run the following code I can see it creates all the listeners, but not even half of them are executed.
fun main(args: Array<String>) {
val random = Random(1000)
runBlocking {
// Create a 1000 event listeners with a random delay of 0 - 1000 ms
for (i in 1..1000)
AsyncEventTest on {
GlobalScope.launch {
delay(random.nextLong())
println(i)
}
}
}
println("================")
runBlocking {
// Trigger the event
AsyncEventTest().emit()
}
}
What am I missing here?
Update
When I remove delay(random.nextLong(), all handlers are executed. This is weird, since I'm trying to simulate different response times from the handlers that way and I think a handler should always execute or throw an exception.
You are running the event listeners with GlobalScope.launch() that does not interact with the surrounding runBlocking() scope. Means runBlocking() returns before all launched coroutines are finished. That is the reason you don't see the output.
BTW: your usage of coroutines and runBlocking is not recommended
You should add suspend to the emit() function. The same is true for the handler parameter - make it suspendable.

Kotlin delegate property by lazy that is thread local

Is there a simple way get a delegated property by lazy's value computed per thread like ThreadLocal?
LazyThreadSafetyMode controls concurrent initialization, with .NONE coming close to the desired functionality by allowing multiple threads to receive different values, but has subsequent post initialization calls referencing the same object, returning the same singular value regardless of thread, with some cases returning null.
Regardless of concurrent initialization, or late initialization, the property would cache a unique value per thread.
The Kotlin delegates are easy to extend with your own implementation.
You can make your delegate maintain a ThreadLocal<T> with initialValue calculated by the function that is passed:
class ThreadLocalLazy<T>(val provider: () -> T) :ReadOnlyProperty<Any?, T> {
private val threadLocal = object : ThreadLocal<T>() {
override fun initialValue(): T = provider()
}
override fun getValue(thisRef: Any?, property: KProperty<*>): T =
threadLocal.get()
}
Or maintain a Lazy<T> per thread with ThreadLocal<Lazy<T>>, so that your delegate can implement Lazy<T> by itself:
class ThreadLocalLazy<T>(val provider: () -> T) : Lazy<T> {
private val threadLocal = object : ThreadLocal<Lazy<T>>() {
override fun initialValue(): Lazy<T> =
lazy(LazyThreadSafetyMode.NONE, provider)
}
override val value get() = threadLocal.get().value
override fun isInitialized() = threadLocal.get().isInitialized()
}
Here's a convenience function to create instances of the delegate:
fun <T> threadLocalLazy(provider: () -> T) = ThreadLocalLazy(provider)
Then just delegate a property to threadLocalLazy { ... }. Usage example:
class Example {
val threadId by threadLocalLazy { Thread.currentThread().id }
}
fun main(args: Array<String>) {
val example = Example()
repeat(3) {
thread {
println(example.threadId) // should print three different numbers
}
}
}