Long story short: with thread pools and no thread pinning it is impossible to have out of the box support for MDC through ThreadLocal
.
If you have something like State
which you'd like to store in every ThreadLocal
, you could have pass around e.g. via implicit state: State
and have something which would set it right before using:
case class State(...)
object State {
private val threadLocal = new ThreadLocal[State] {
def initialValue() = null.asInstanceOf[State] // or some default
}
def get: State =
Option(threadLocal.get()).getOrElse(
throw new IllegalStateException("You haven't propagated the state!")
)
def set(implicit state: State): Unit =
threadLocal.set(state)
}
def futureA(implicit state: State): Future[B] = Future {
State.set // obligatory, used in a way you know that it will be called
// on the same thread as the rest of the Future's body
somethingThatAccesesStateGet
}
It's supper brittle, error-prone but it's the only way with Future.
In Monix one could use TaskLocal
to propagate values around instead of arguments in every function - as described in this port and implemented in this library. I you haven't heard of Monix, migrating your whole codebase to it right now might not be a good idea - you'd have to replace every usage of Future
in your code with Monix Task
. Especially, since they haven't been ported to Cats Effect 3 yet.
In Cats Effect 3, since Monix hasn't been ported to it, one would have to work with IOLocal
, and some custom made mechanics to propagate the state. I've done it with tagless final and custom decorator over Async[IO]
type class. If you haven't heard of: Cats Effect 3, type classes or tagless final, this solution might also be of no interest to you, since you'd have to replace every Future
with an abstract F
and then interpret it with decorated Async[IO]
- which runs Cats Effect IO, not the Future
.
(You can convert to and from the Future in both of these cases, which is useful on the edges of some libraries, but as a principle everything would have to be done with these libraries' Future
alternatives).
Assuming that you are working with Futures, I don't see a way you could avoid propagating the values to where you use them in other way than just by passing them around through arguments. Whether it will be explicit, implicit or through some ReaderT
with fixed input, would affect ergonomics but not the principle.