:wave: it is indeed a coroutine question :slightly...
# graphql-kotlin
d
👋 it is indeed a coroutine question 🙂 TLDR its all about switching threads
Copy code
1 MDC.put("k1", "v1")
2 <http://logger.info|logger.info>("one")
3 withContext(<http://Dispatchers.IO|Dispatchers.IO> + MDCContext()) {
4   MDC.put("k2", "v2")
5   <http://logger.info|logger.info>("two")
6   delay(10000) // Fake an API call
7   <http://logger.info|logger.info>("three")
8 }
9 <http://logger.info|logger.info>("four")
given the above I believe the behavior would be - • when calling the function we got initial MDC value • lines 1&2 will run on the same thread with updated MDC
{ "k1": "v1" }
• then at line 3 we start the coroutine which MAY change the thread • our MDC context of this coroutine is
{ "k1": "v1" }
• 4 & 5 will run on the same thread with modified MDC
{ "k1": "v1", "k2": "v2" }
• 6 is suspendable call and once it returns it may resume on a different thread • 7 resets the MDC back to
{ "k1": "v1" }
(from line 3) • we are back to main logic where we may run on a different thread which then uses MDC context from when we entered the function (i.e. initial MDC) and yes in order to have consistent logging behavior you would have to restore the MDC after each suspension point see the docs -> https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-slf4j/kotlinx.coroutines.slf4j/-m-d-c-context/
o
Thank you for the reply! I had looked at the example below the text
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.
but based on that example I had assumed the MDCContext with
{ "k1": "v1" }
would have still existed in line 9 since that was not coming from an update inside the coroutine on line 3 Is it a common practice then to add a helper method to restore the MDC after each suspension point? Such as something like
Copy code
suspend fun withMDCContext(val dispatcher: Dispatcher) {
val contextMap = MDC.getCopyOfContextMap()
withContext(dispatcher + MDCContext()) {
  // API call
 }
MDC.setContextMap(contextMap)
}
Or by doing this in a
suspend
fun do I still run the risk of the MDCContext being lost?
d
it all really depends how you structure your code
AFAIK if you need to correctly propagate the MDC (or other ThreadLocals) then you have to do explicit setting after each suspension point
coroutine context is immutable -> the coroutine state machine keeps track of the context on each suspension points and uses that to restore the context for the continuation so in your example • [1] you got a context entering the function ◦ [2] you modify it and start another one coroutine using
withContext
▪︎ [3] invoke another suspendable function (
delay
) ◦ when you return after calling the delay you get context back from [2] (i.e. MDC with k1 only) • when you exit out of
withContext
you get the context back from [1] (thats why you dont get those extra MDC values there and you only get whatever was set in the context factory)
o
I see, thank you for the responses this was very helpful!