https://kotlinlang.org logo
Title
t

Tomer Abramovich

02/09/2022, 6:54 AM
hi, new to kotlin, so bear with me... i'm building a library which relies on coroutine context (via suspend functions) to propagate "sticky" information such as tenant id etc. what is the best way for the caller of my library function to pass the tenant information on the context?
suspend fun myLibraryFunction(query : Query) : QueryResult{
  val requestContext: RequestContext = currentCoroutineContext()[RequestContext]!! //expecting a dedicated context element
  val tenantId = requestContext.tenantId
  do something with tenantId
}

suspend callerFunction () {
  val requestContext = RequestContext(tenantId)
  CoroutineScope(requestContext).async { myLibraryFunction(query) }.await()
}
^^^ This is what i came up with, but it seems redundant to create a new coroutine (i.e async) i just want the user to be able to set the requestContext on the current context and that the called function will inherit that something like
suspend fun callerFunction () : QueryResult {
  val requestContext = RequestContext(tenantId)
  currentCoroutineContext().plus(requestContext) //i know it's immutable so won't work
  myLibraryFunction(query)
}
b

bezrukov

02/09/2022, 6:57 AM
Use
withContext(requestContext) { myLibFun(...) }
1
t

Tomer Abramovich

02/09/2022, 7:00 AM
thanks for the quick reply! i tried that as well before the async/await variant but got this sonar warning: SonarLint: Remove this dispatcher. It is pointless when used with only suspending functions. i assume that async/await also uses a dispatcher behind the scenes though.. is there a better option than this "withContext"? basically i have a suspended function calling another suspended function, why would i need a coroutineScope for that? can't the context be inherited implicitly?
b

bezrukov

02/09/2022, 9:22 AM
I didn't work with sonar lint so I don't know what does this warning mean, especially since this piece of code doesn't use any dispatcher.
i assume that async/await also uses a dispatcher behind the scenes though.. is there a better option than this "withContext"?
As I said, it's unclear what wrong with dispatcher stuff in your code.
withContext
is a default way to modify context of execution (it takes your current context and adds requestContext to it) Problem here
CoroutineScope(requestContext).async { myLibraryFunction(query) }.await()
It doesn't follow structured concurrency, cancellation of callerFunction won't cancel myLibraryFunction because you called it in completely different context. This context doesn't inherit Job/Dispatcher etc. If you by some reason want to stick with await/async (I do not recommend that for this usecase), you need to use
coroutineScope { }
builder:
fun caller() = coroutineScope {
   async(newContextElements) { innerFun() }.await()
}
// OR
fun caller() = coroutineScope {
   launch(newContextElements) { innerFun() }
}
But again, these are just examples, they are not perfect for your usecase. Best option is
fun caller() = withContext(newContextElements) {
   inner()
}
t

Tomer Abramovich

02/09/2022, 9:43 AM
thanks a lot for the detailed answer! i will follow the "withContext" path