Is there a way to make a function that is polymorp...
# coroutines
d
Is there a way to make a function that is polymorphic based on whether or not it is called with a suspend block? ie, i have a function
withSomething
that either can be passed a non-suspend block and can be called from either a suspend or non-suspend block and basically does some sort of
try { block() } finally { … }
, and a suspend inline function
withSomethingSuspend
that can be passed a suspend block and does a more complex
withContext
-based trick to manage the same thing. right now you have to choose to use the correct function name for your context; we made the first function non-inline so that you can’t accidentally call it with a suspend block. is there any way we could have just one function name? or at least declare that the first one’s block can’t be suspend so that we could make it inline?
o
depending on what the function is actually doing, there may be a better solution than being polymorphic on
suspend
d
this is for slf4j MDC stuff. i know about MDCContext that ships with coroutines that lets you easily ensure that newly-launched coroutines keep the MDC context. the helper is something that adds some fields to the context map for the duration of the block
Copy code
fun <T> withLoggingFields(contextMap: Map<String, Any?>?, block: () -> T): T {
 val oldContextMap: Map<String, String>? = MDC.getCopyOfContextMap()
 MDC.setContextMap((oldContextMap ?: mapOf()) + (contextMap?.mapValues { it.value.toString() } ?: mapOf()))
 return try {
  block()
 } finally {
  if (oldContextMap != null) MDC.setContextMap(oldContextMap) else MDC.clear()
 }
}
and
Copy code
suspend inline fun <T> withLoggingFieldsSuspend(
	contextMap: Map<String, Any?>?,
	crossinline block: suspend CoroutineScope.() -> T
): T = withContext(
	MDCContext((MDC.getCopyOfContextMap() ?: mapOf()) + (contextMap?.mapValues { it.value.toString() } ?: mapOf()))
) { block() }
o
hmm, I can't think of a good way to make that work -- the closest I can think of is marking the non-suspend variant with
crossinline
, which will forbid it from being
suspend
I think
https://youtrack.jetbrains.com/issue/KT-17260 is open, but I don't think a good use case has been mentioned there, you might add this to the issue
d
That suggestion would mean we could make the first function
inline
basically?
o
mostly inline, yes
d
is the crossineline-can’t-be-suspend thing documented?
o
I don't know if it's explicitly documented, but it's a logical conclusion -- the reason the normal inline works is that it inherits the context of the function you're calling from, it can't work with
crossinline
because that implies you're not in the same context
j
Note that you cannot update MDC context from inside of the coroutine simply using MDC.put. These updates are going to be lost on the next suspension and reinstalled to the MDC context that was captured or explicitly specified in contextMap when this object was created on the next resumption. Use withContext(MDCContext()) { ... } to capture updated map of MDC keys and values for the specified block of code.
d
@Joe huh, MDC is thread-local state — how does it work with coros then?
o
the secret is that the
withContext
element just stores it into the current thread's thread-local
j
(we also launch all the coroutines with MDCContext(), which probably explains that)
d
hmm, i thought that all MDCContext did was capture the MDC state once
d
or do you only call your withRequestId outside of the MDCContext creation?
j
yeah i think that's it -- we've already wrapped before creating the MDCContext: withRequestId() { ... launch (context + MDCContext()) {// stuff}}