https://kotlinlang.org logo
#coroutines
Title
# coroutines
j

JoeHegarty

10/15/2018, 9:08 PM
Is there a way to say "give me the coroutine context if I am in a coroutine"? I basically want/need a way to detect if I am running in a coroutine and to grab the context, even in a non suspending method. It that possible?
d

Dominaezzz

10/15/2018, 9:14 PM
You can use the
coroutineContext
property but only in a suspend function or a
CoroutineScope
.
j

JoeHegarty

10/15/2018, 9:16 PM
Yeh, I am looking for a way to detect if I am in a coroutine so I can't call a suspending property
I know it's kind of a weird edge case, but I was hoping there was a way to do it.
d

Dominaezzz

10/15/2018, 9:17 PM
You could declare your method an extension of
CoroutineScope
?
Especially since a coroutine cannot exist outside of a
CoroutineScope
.
j

JoeHegarty

10/15/2018, 9:22 PM
But I have no way of getting it right? What I want to be able to do is something like
Copy code
fun hello(): CoroutineContext? {
    if(isInCoroutine) {
         return coroutineContext
    } else {
         return null
    }
}
In this case
hello
is not suspending so I have no access to any of the usual context stuff
But if
hello
was called from within a coroutine then I want to be able to get that context.
I assume there is something, a ThreadLocal or something that gets copied around as a coroutine jumps across threads etc, I just want to know if there is a way to access that.
d

Dominaezzz

10/15/2018, 9:32 PM
No, there's no way to access that outside a suspend function or a
CoroutineScope
.
j

JoeHegarty

10/15/2018, 9:32 PM
That is... disappointing.
d

Dominaezzz

10/15/2018, 9:36 PM
Mind if I ask for your use case?
j

JoeHegarty

10/15/2018, 9:40 PM
So, I have a scenario where I have an existing application that has this concept of a request context type thing, we call it a task context. People write their own methods that get called by our framework and they may call say TaskContext.get() to get some information about the request on top of what we pass in. We are in the process of moving some of our internal framework infrastructure over to coroutines, so we're using the coroutineContext to store what used to be the Task context. In order to avoid having to basically set a thread local every time we call into user code I was hoping there was a system to already do it so TaskContext could just get the coroutine that it is running in.
We know the user code must be running in a coroutine because we invoke it, but we can't require our users to use suspending functions because a lot of them use Java/Scala etc
So right now, we have to basically wrap any user code call with a threadlocal set just for this
And while I know it's a very specific edge case, the JVM/Kotlin must know it's in a coroutine so the fact there is no way to grab that is kind of annoying.
d

Dominaezzz

10/15/2018, 9:47 PM
TaskContext
is a class with a static
get
method?
j

JoeHegarty

10/15/2018, 9:49 PM
Yeh. So basically right now I have like
Copy code
suspend fun someFrameworkFunction() {
 	TaskContext.push(ourSpecialCoroutineContext)
 	someUserFunction()
 	TaskContext.pop()
}

fun someUserFunction() {
	println(TaskContext.get().requestId)
}
But obviously I need to do that wrap every time I transition from suspend -> non suspend function
Would be nice if I could just make
TaskContext.get()
get the coroutineContext
d

Dominaezzz

10/15/2018, 9:53 PM
Why not pass the TaskContext object as a parameter?
j

JoeHegarty

10/15/2018, 9:54 PM
Oh, that is getting into really messy territory but the short answer is I can't 😛
The user defines the API
d

Dominaezzz

10/15/2018, 9:55 PM
Oh, okay. Bummer.
j

JoeHegarty

10/15/2018, 9:56 PM
So it's not like the user implements an interface we control, it can be anything they want.
Basically, think remoting, so the user creates their own interfaces/classes and then we provide the plumbing to call across network. It's more complex than that but you get the idea.
g

gildor

10/16/2018, 12:11 AM
You cannot, maybe only if you use some stack trace walking and reflections
Actually, one more solution, but not so robust, always use special coroutines-only thread pools, so you can check at least thread name or some flag in threadlocal (but requires to set it, maybe as part of custom thread factory)
j

JoeHegarty

10/16/2018, 5:25 AM
But even if I did that, I wouldn't be able to get the context right? So I'd still need some magic there.
So I think you are right, I could only do it with reflection or similar, and that seems... messy.
I can in general see this as a feature that might be useful for library devs like myself as we often want to know what kind of context we're running in whether it be kotlin coroutines, reactor etc.
g

gildor

10/16/2018, 5:28 AM
But even if I did that, I wouldn’t be able to get the context right
Sure, only if you explicitly set it to ThreadLocal, but this is super messy, becaue one thread can handle many contexts, this is how coroutines work: reuse threads
j

JoeHegarty

10/16/2018, 5:29 AM
Yeh, it's a tricky one.
g

gildor

10/16/2018, 5:30 AM
I’m still not sure when you need this. Of course it can be useful in some tricky cases, but looks more like hack/workarond and probably make sense to reconcider your architecture
j

JoeHegarty

10/16/2018, 5:31 AM
Not sure I agree with that. It's not a hack, assuming I could tell I was ultimately in a coroutine and get the context.
It might not be the "kotlin way" but it is a common pattern in Java
And as a library maintainer I have to think about those folks using Java, Scala etc
g

gildor

10/16/2018, 5:32 AM
Problem that common (non-suspend) functions doesn’t have any context except Thread (which can be reused by multiple coroutines) and Stacktrace
so you have not so many ways to do something
Of course, case to support non-suspend functions is very important, no doubts there, I just not sure about case when you have non-suspend client function and you need coroutine context inside
j

JoeHegarty

10/16/2018, 5:33 AM
Yeh, well at that point we're getting into the internal details of how coroutines are implemented. Like, for example how does Kotlin actually keep track of the context internally? Does it simply always pass it around, does it set a thread local before the scheduler schedules each task etc
I don't know that info.
g

gildor

10/16/2018, 5:34 AM
Yes, Kotlin always pass it to each suspend function using continuation argument
to reproduce this in Java you actually need this additional argument with context or with continuation
And it doesn’t set thread local of course, it doesn’t work properly with coroutines, only continuation
j

JoeHegarty

10/16/2018, 5:35 AM
Well, it would work if it did it before each continuation until it suspends and control goes back to the scheduler
Which is how reactor etc does it
But yes, if it really does just pass it around (which in general seems smart because it's cheap and who cares if it doesn't pollute the code) then there is no way to get it from a non suspending function.
So I probably do need to just wrap all my handoffs to user code and grab it while I still can
g

gildor

10/16/2018, 5:40 AM
Well, it would work if it did it before each continuation until it suspends and control goes back to the scheduler
yes fair point, looks that it’s possible, and even can be implemented with custom dispatcher, because coroutine dispatcher is just library class
j

JoeHegarty

10/16/2018, 5:41 AM
Yeh, i am thinking that's the right approach. Either in my library code before I hand off, or a custom coroutine dispatcher. Thanks for your help!
Also, maybe make sense to describe your use case and create an issue on kotlinx.coroutines, who nows, maybe it’s possible to have some hook for your use case
d

Dico

10/16/2018, 1:34 PM
You can use a
ThreadLocalContext
. Make a global ThreadLocal that's updated with the context whenever the coroutine thread changes, and cleared when the coroutine completes. You just need to add it to your context whenever you launch a coroutine.
I haven't tested it but I don't see why it wouldn't work.
The stuff with the dispatchers is actually very complex to implement, but coroutines library provides a unit to do it.
Actually, it's not that simple unfortunately.
g

gildor

10/16/2018, 2:08 PM
Yes, Dico is absolutely right, just found ThreadContextElement API https://github.com/Kotlin/kotlinx.coroutines/commit/7587eba96a230f3f0ab0c04f52723e926f7a845d
d

Dico

10/16/2018, 2:35 PM
z

Zach Klippenstein (he/him) [MOD]

10/16/2018, 2:37 PM
We are in the process of moving some of our internal framework infrastructure over to coroutines, so we're using the coroutineContext to store what used to be the Task context.
It sounds like you had a way of passing your own context around before that worked. Why not just keep that architecture? Sounds like you're trying to use coroutine context for more than it was intended. Coroutine context is just that - the context of a coroutine - if you have your own concept of context for your own architecture it probably makes sense to keep modelling that explicitly.
d

Dico

10/16/2018, 2:44 PM
If they used a
ThreadLocal
before, they need to use
CoroutineContext
now because coroutines can change thread. This solution might not use the coroutine context the way it was intended, that doesn't make it inherently bad. Either that, or they have to add parameters for the task context all over the code base (if they aren't there yet...) They could also use
ThreadContextElement
to store
TaskContext
instead of the
CoroutineContext
.
z

Zach Klippenstein (he/him) [MOD]

10/16/2018, 3:01 PM
Either way, sounds like this system is trying to use some clever tricks to implicitly provide access to a very important part of its API and that's why it feels so awkward. Ideally the architecture would be more explicit about how the context is stored/owned, but I guess that sort of change is out of scope?
d

Dico

10/16/2018, 3:02 PM
You are right. I'm not designing it.
if long call chains lose track of the
TaskContext
they could possibly be implemented with an object that keeps a reference the task context.
j

JoeHegarty

10/16/2018, 5:31 PM
Yeh. I’m not saying the API is necessarily correct just that we need to continue to support it. You can think of a Task as being broadly similar to a coroutine and the TaskContext is our version of the coroutine context or thread locals if you were doing it manually. Changing the customer facing API is a non starter so I need to replicate the existing behaviour.
d

Dico

10/16/2018, 5:35 PM
Hope the thing in the gist I linked works for you then
j

JoeHegarty

10/16/2018, 5:35 PM
I’m not sure the API is necessarily bad though. It’s very similar to things like request context when you are using spring, jax-rs etc. Or Akka context.
So it seems like a fairly common pattern in the Java world.
If I were designing the API myself I wouldn’t do it, but the reasoning behind it seems like “ it’s a common way to do it”.
4 Views