Affects: 5.3.7


I am using the CoRouterFunctionDsl to build a Spring Webflux application with suspend handlers in Kotlin. Recently I wanted to add some SLF4J MDC values (e.g. an AWS X-Ray Trace id) which requires the usage of https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-thread-context-element/. MDC is ThreadLocal based which does not play well with coroutines.

Adding such a ThreadContextElement can be done in a generic way (so it doesn't have to be repeated in each handler) in the private method asHandlerFunction in the class CoRouterFunctionDsl:

mono(Dispatchers.Unconfined) {
    init(it)
}

but there is no way of doing that. For now I copied the class and changed the function myself. The data I need there is based on the subscriber context of the Mono (coming from a WebFilter) and the ServerRequest, so it would be neat to inject something like coroutineContextProvider: (req: ServerRequest) -> Mono<CoroutineContext> into the DSL:

private val contextProvider = (req: ServerRequest) ->
  Mono.deferContextual { ctx ->
    // Extract data from req and ctx
    // Return a coroutine context
  }

coRouter(contextProvider) {
  GET("/info", ...)
}

// In CoRouterFunctionDsl

private fun asHandlerFunction(init: suspend (ServerRequest) -> ServerResponse) = HandlerFunction { req ->
    contextProvider(req)
      .flatMap { coroutineContext ->
              mono(coroutineContext) {
                  init(req)
              }
      }
}

I hope that makes sense. If there is an easier solution that I oversaw I am of course also open to that.

Comment From: poutsma

@sdeleuze Could you take a look at this one, please?

Comment From: sdeleuze

@poutsma Do we provide a way to do the same thing on Reactive side via Reactor context? If not, what would that look like and would it b something we want to implement?

Comment From: FrontierPsychiatrist

@sdeleuze I think in a pure Reactor implementation this is easier since the call chain "stays inside the framework". It can be easily done by adding a WebFilter that uses Mono.contextWrite. Adding a filter in coRouter does not have the same effect as there will be another "switch" back to Reactor after the filter and the coroutine context will be "forgotten".

Comment From: sdeleuze

I am not sure how this should be implemented and exposed, but indeed getting access to the CoroutineContext seems a valid use case.

Comment From: sdeleuze

I am considering adding a fun context(provider: suspend (ServerRequest) -> CoroutineContext) method to the DSL in Spring Framework 6.1 to provide a custom CoroutineContext in various places of the DSL. It would allow to write code like:

coRouter {
  context { request ->
    delay(1) // Can invoke suspending functions
    CoroutineName(request.headers().firstHeader("Custom-Header")!!)
  }
  GET("/") {
    // ...
  }
}

Comment From: FrontierPsychiatrist

That seems like pretty much exactly what would help me! E.g. something like this is what I do at the moment)

coRouter {
  context { request ->
    MDC.put("traceId", req.header("traceId"))
    MDC.put("userId", req.awaitPrincipal().name)
    // Not sure if NonCancellable will work like this, in my tests the mono() creator did not accept it
    kotlinx.coroutines.slf4j.MDCContext() + NonCancellable
  }
  // mappings
}

Comment From: sdeleuze

Merged, please try 6.1.0-SNAPSHOT builds once created by the CI and provide a feedback if possible.

Comment From: FrontierPsychiatrist

I just tested it in a small application and it worked fine!