I want to ask as why MDCContext got empty after calling awaitSingle of a Mono
here is snippet of the code
suspend fun getTemplates(request: GetPaginatedTemplateServiceRequest): PaginationTemplates {
var context = MDCContext()
val count = templateRepository.findByName(
request.templateName
).awaitSingle()
context = MDCContext()
}
for your information templaterepository is using ReactiveMongo.
the first call of MDCContext, I have some x-request-id
in the context map which i generated before that function
but after i call that awaitSingle with Mono, the MDCContext got empty (which I am not expected)
One solution that I try is to wrap awaitSingle with withContext function
example below
var context = MDCContext()
val count = withContext(Dispatchers.IO.plus(MDCContext())) {
templateRepository.findTemplatesCount(
request.templateName
).awaitSingle()
}
context = MDCContext()
then the second call of mdc context still containing items like the first call (which i expected).
I actually can use that solution, but there is so many already code that use awaitSingle like that.is there any simple solution for that? might be like automatically passing context to mono or something?
thankss, let me know if it is not clear enough
The MDCContext
needs to be explicitly managed across the asynchronous boundaries. Unfortunately, ReactiveMongo
does not inherently handle the propagation of this context.
There is not an automatic way to pass the context to every Mono
operation, unless the reactive library explicitly supports this context propagation.
Some reactive frameworks might automatically propagate contexts, while others might not.
In essence, the current solution of using withContext
is the most practical method available to maintain the context across awaitSingle()
calls.
Continue using the withContext
method you've already implemented to ensure context propagation; and consider encapsulating this into reusable utilities wrapping the asynchronous operations that require the context.
The following is a draft example of encapsulating the context propagation logic.
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.slf4j.MDC
import reactor.core.publisher.Mono
suspend fun <T> withMdcContext(block: suspend () -> T): T {
val contextMap = MDC.getCopyOfContextMap() ?: emptyMap()
return withContext(Dispatchers.IO + MDCContext(contextMap)) {
block()
}
}
// Extension function for context preservation
fun <T> Mono<T>.preserveContext(): Mono<T> =
this.subscriberContext { context ->
val mdcContext = context.get(MDCContext::class.java)
context + (mdcContext ?: MDCContext())
}
Calling preserveContext()
explicitly ensures that any context set before the reactive operation remains throughout the whole reactive chain.
It's a proactive step to safeguard context integrity in scenarios where the default behavior of context propagation might not be consistent across various reactive libraries.
suspend fun getTemplates(request: GetPaginatedTemplateServiceRequest): PaginationTemplates {
val count = withMdcContext {
templateRepository.findByName(request.templateName)
.preserveContext() // Ensure context preservation in Mono
.awaitSingle()
}
// ...
}