Kjartan
08/06/2025, 12:05 PMcall
property which as far as I understand does what I want:
call.launch(Dispatchers.IO) {
runCatching {
// some "long running" side effect doing updates in the db that should keep running after the response has been sent back to the client.
}.onFailure { error ->
// catch to avoid failing the parent coroutine and log properly
}
}
Is this considered "best practice" for my task? What about launching the coroutine in a new scope not related to the call/route scope:
CoroutineScope(Dispatchers.IO).launch {
// same as above
}
What is considered best practice?phldavies
08/06/2025, 12:08 PMmadisp
08/06/2025, 12:13 PMserviceScope
that we pass around with DI. When the container is getting drained (e.g. a new release rolls out) the scope is canceled, triggering shutdown of things like queue listeners and graceful closure of long-living connections like websockets etcsimon.vergauwen
08/06/2025, 12:23 PMCoroutineScope
. TL;DR never do create an custom scope, nor use GlobalScope
. Especially with Ktor its not needed.
There is 2 CoroutineScope
available to you in Ktor.
1. The Application : CoroutienScope
scope which works like @madisp described, it's available when the application starts and gets cancelled and awaited when the engines or JVM exit. Instead of creating a serviceScope
I pass around the Application
but I encapsulate it as CoroutineScope
to my service layer as also discussed in the Ktor talk. The recording is online btw, .
2. The RoutingCall : CoroutineScope
which is available as the call
property in any RoutingHandler
.phldavies
08/06/2025, 12:38 PMapplication
from call
and do call.application.launch { ... }
Kjartan
08/07/2025, 7:39 AMKjartan
08/07/2025, 9:58 PMApplication : CoroutineScope
as it makes more sense to me than using the RoutingCall : CoroutineScope
scope:
call.application.launch {
runCatching {
sideEffectService.performLongRunningTask()
}.onFailure { error ->
// some error handling
}
}
All coroutines created within `performLongRunningTask()`will then be a children of the Application : CoroutineScope
and to my understanding will be awaited if the applications shuts down while this operation is running.
However it is a bit unclear to me why I would want to pass around the Application : CoroutineScope
or some serviceScope
? I suppose this is only relevant if if I want to create coroutines that should not be tied to the current "parent" CoroutineScope
?
So in my example above I am already using the Application : CoroutineScope
and it would not make sense if that also was injected as a private variable in the sideEffectService
, Unless I would have a specific reason for wanting to create some other side effect that should not be related to the "outer" CoroutineContext. Or are there other reasons for injecting a CoroutineScope?simon.vergauwen
08/08/2025, 6:47 AMI suppose this is only relevant if if I want to create coroutines that should not be tied to the current "parent"I'm not entirely sure what you mean, but?CoroutineScope
Application
is the parent of the ApplicationCall : CoroutineScope
. Yes, injecting the CoroutineScope
into your service layer, or relying on it directly like you're doing is only needed when you want to create new coroutines. Or in other words parallel executing code. You normally always want to prefer simple suspend
functions otherwise, or regular functions for non-suspending code of course.
So in my example above I am already using theGreat question! Your use-case and usage is perfect. It's a maybe bit subjective, but some prefer injecting into the class itself. This can be desired when you want to encapsulate the behavior of launching, to make the function available to Java code, ... ? In your snippet the service seems to exposes a simpleand it would not make sense if that also was injected as a private variable in theApplication : CoroutineScope
, Unless I would have a specific reason for wanting to create some other side effect that should not be related to the "outer" CoroutineContext. Or are there other reasons for injecting a CoroutineScope?sideEffectService
suspend fun
, which makes the complexity of the service lower imo, and defers the decision how the function runs to the user. Your approach also significantly lowers the testing complexity, since no injecting of context is required