How should I pass coroutine-local data to a synchr...
# coroutines
m
How should I pass coroutine-local data to a synchronous method without passing the data directly? I tried using ThreadLocals with ThreadLocal.asContextElement but it doesn't seem to update the ThreadLocal across dispatchers,
myThreadLocal.get()
simply reports as null even though it shouldn't be, as the sync call is nested far down inside
coroutineContext(myThreadLocal.asContextElement(nonNullValue)) { /* get happens inside here */ }
My use-case is a custom JDBC driver wrapper providing SQL trace data to the current coroutine by modifying a MutableList.
d
it doesn't seem to update the ThreadLocal across dispatchers
But it does. https://pl.kotl.in/GzEXDglfe
coroutineContext(myThreadLocal.asContextElement(nonNullValue)) {
I don't know where this
coroutineContext
function comes from, but it's not from
kotlinx.coroutines
, as our
coroutineContext
only accepts the lambda argument and nothing else.
m
sorry, I meant
withContext
, my bad.
Hmm, in that case, would you happen to know how to debug these threadlocals disappearing when it works fine when I don't use Dispatchers.IO?
d
https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/as-context-element.html explains in detail how
asContextElement
behaves, so it could be a good starting point. If the documentation is somehow incorrect/misleading, please let us know, and we'll fix it.
m
I guess there must be something weird going on in Exposed then; The threadlocal should be in scope from a prior withContext call (as shown by the 1st println) but in the second context it seems to just be gone completely.
d
I hope someone more experienced with Exposed could chime in. I don't know what
newSuspendedTransaction
does exactly. As a
kotlinx.coroutines
maintainer, I'm suspicious of `coroutineContext + Dispatchers.IO`: usually, doing
coroutineContext +
shouldn't be needed, each operation should preserve the surrounding context. Does
newSuspendedTransaction(<http://Dispatchers.IO|Dispatchers.IO>)
also not work?
m
Thast's what I originally had and also doesn't seem to work
It seems setting it to
<http://Dispatchers.IO|Dispatchers.IO> + tls.asContextElement(tls.get())
does work however.
d
Then it looks like your original context doesn't contain
tls.asContextElement
. Otherwise,
coroutineContext + <http://Dispatchers.IO|Dispatchers.IO>
should have worked.
Looking at https://github.com/JetBrains/Exposed/blob/7e5d03fedefe0de95156e8409efdf79ed273e7e9[…]rg/jetbrains/exposed/sql/transactions/experimental/Suspended.kt, I see that Exposed indeed preserve the outside context for some reason. I'm not knowledgeable enough to say whether it's a mistake on their part, but it does look strange. In any case, they do document this behavior: https://github.com/JetBrains/Exposed/blob/7e5d03fedefe0de95156e8409efdf79ed273e7e9[…]rg/jetbrains/exposed/sql/transactions/experimental/Suspended.kt
m
Then it looks like your original context doesn't contain
tls.asContextElement
. Otherwise,
coroutineContext + <http://Dispatchers.IO|Dispatchers.IO>
should have worked.
I'm not sure how that happens, throwing an exception clearly shows it inside the
withContext(tls.asContextElement(...))
so it should be there
Copy code
var wrapper: suspend (content: suspend () -> Unit) -> Unit = { it() }

wrapper = {
    // tls no longer in coroutineContext according to IntelliJ debugger
    newSuspendedTransaction(Dispatchers.IO) {
        it()
    }
}

// ...

// tls in coroutineContext
async {
    // tls in coroutineContext
    wrapper {
        // ...
It looks like the suspending function as property may have some weird handling.
Yet a minimal reproducer doesn't seem to have the same issue, so I'm completely lost on what to do here.
d
If this is a real problem for you, you could share your project where the issue reproduces (privately with JetBrains or publically) along with clear instructions on how to reproduce the issue there, and we could take a look. Otherwise, you could try obtaining a reproducer this way: copy your whole project, then remove code piece by piece until removing anything else stops the issue from appearing. That's what we typically do with the projects sent to us for examination anyway.
m
The problem with the latter is that it's happening only in an application I'm working on, but the context dropping seems to happen in a separate library I develop. How should I go about reporting this?
d
You could copy the library and the application into a single reproducing project.