Launching coroutine in Ktor request handler - kotlin

I want to instantly respond to the client and handle the request in the background by launching a coroutine. First i tried the following solution:
suspend fun PipelineContext<Unit, ApplicationCall>.handleTest(value: Unit) {
// insert calls to suspending functions here
launch {
repeat(10000) {
println("Executing background task $it.")
delay(1000)
}
}
call.respond("Executing the task in background")
}
routing {
get("/test", PipelineContext<Unit, ApplicationCall>::handleTest)
}
This works as expected. It returns instantly and executes the background task.
Though, IntelliJ IDE gives me the following warning:
Ambiguous coroutineContext due to CoroutineScope receiver of suspend function
I know what this warning means and why it occurs, so I tried to find a way around this:
suspend fun handleTest(context: PipelineContext<Unit, ApplicationCall>) {
// insert calls to suspending functions here
context.launch {
repeat(10000) {
println("Executing background task $it.")
delay(1000)
}
}
context.call.respond("Executing the task in background")
}
routing {
get("/test") {
handleTest(this)
}
}
This piece of code also works as expected, however it looks wrong to me when reading this article, https://elizarov.medium.com/explicit-concurrency-67a8e8fd9b25. The author explains that you should not launch coroutines inside a suspending function unless wrapping it in a new coroutineScope {}.
I was curious and tried out to inline handleTest:
routing {
get("/test") {
// insert calls to suspending functions here
launch {
repeat(10000) {
println("Executing background task $it.")
delay(1000)
}
}
call.respond("Executing the task in background")
}
}
This also works as expected, and even the warning is gone. However, the construct is theoretically still the same as in the first solution.
What is the correct solution to my problem?

You can use CoroutineScope:
routing {
get("/test") {
// insert calls to suspending functions here
CoroutineScope(Job()).launch {
repeat(10000) {
println("Executing background task $it.")
delay(1000)
}
}
call.respond("Executing the task in background")
}
It will create a new scope decouple the handler context.

I suggest creating a new coroutine scope to make it clear from where the coroutineContext is taken because both CoroutineScope and suspend function have it.
suspend fun PipelineContext<Unit, ApplicationCall>.handleTest(value: Unit) = coroutineScope {
// insert calls to suspending functions here
launch {
repeat(10000) {
println("Executing background task $it.")
delay(1000)
}
}
call.respond("Executing the task in background")
}

Related

Kotlin Coroutines: what's the diffrence between using NonCancellable and a standalone new Job

In Coroutines, when I want to guard a block of code against cancellation, I should add NonCancellable to the Context:
#Test
fun coroutineCancellation_NonCancellable() {
runBlocking {
val scopeJob = Job()
val scope = CoroutineScope(scopeJob + Dispatchers.Default + CoroutineName("outer scope"))
val launchJob = scope.launch(CoroutineName("cancelled coroutine")) {
launch (CoroutineName("nested coroutine")) {
withContext(NonCancellable) {
delay(1000)
}
}
}
scope.launch {
delay(100)
launchJob.cancel()
}
launchJob.join()
}
}
The above unit test will take ~1.1sec to execute, even though the long-running Coroutine is cancelled after just 100ms. That's the effect of NonCancellable and I understand this point.
However, the below code seems to be functionally equivalent:
#Test
fun coroutineCancellation_newJobInsteadOfNonCancellable() {
runBlocking {
val scopeJob = Job()
val scope = CoroutineScope(scopeJob + Dispatchers.Default + CoroutineName("outer scope"))
val launchJob = scope.launch(CoroutineName("cancelled coroutine")) {
launch (CoroutineName("nested coroutine")) {
withContext(Job()) {
delay(1000)
}
}
}
scope.launch {
delay(100)
launchJob.cancel()
}
launchJob.join()
}
}
I tried to find any functional differences between these two approaches in terms of cancellation, error handling and general functionality, but so far I found none. Currently, it looks like NonCancellable is in the framework just for readability.
Now, readability is important, so I'd prefer to use NonCancellable in code. However, its documentation makes it sound like it is, in fact, somehow different from a regular Job, so I want to understand this aspect in details.
So, my quesiton is: is there any functional difference between these two approaches (i.e. how can I modify these unit tests to have difference in outcomes)?
Edit:
Following Louis's answer I tested "making cleanup non-cancellable" scenario and in this case Job() also works analogous to NonCancellable. In the below example, unit test will run for more than 1sec, even though the coroutine is cancelled just after 200ms:
#Test
fun coroutineCancellation_jobInsteadOfNonCancellableInCleanup() {
runBlocking {
val scope = CoroutineScope(Job() + Dispatchers.Default + CoroutineName("outer scope"))
val launchJob = scope.launch(CoroutineName("test coroutine")) {
try {
delay(100)
throw java.lang.RuntimeException()
} catch (e: Exception) {
withContext(Job()) {
cleanup()
}
}
}
scope.launch {
delay(200)
launchJob.cancel()
}
launchJob.join()
}
}
private suspend fun cleanup() {
delay(1000)
}
NonCancellable doesn't respond to cancellation, while Job() does.
NonCancellable implements Job in a custom way, and it doesn't have the same behavior as Job() that is using cancellable implementation.
cancel() on NonCancellable is no-op, unlike for Job() where it would cancel any child coroutine, and where any crash in the child coroutines would propagate to that parent Job.

Unable to Execute code after Kotlin Flow collect

I'm trying to execute some code after calling collect on a Flow<MyClass>. I'm still kind of new to using Flows so I don't understand why the code after the function doesn't get called.
How I use the Flow:
incidentListener = FirebaseUtils.databaseReference
.child(AppConstants.FIREBASE_PATH_AS)
.child(id)
.listen<MyClass>() //This returns a Flow<MyClass?>?
How I consume the Flow:
private suspend fun myFun() {
viewmodel.getListener()?.collect { myClass->
//do something here
}
withContext(Dispatchers.Main) { updateUI() } //the code never reaches this part
}
How myFun() is called:
CoroutineScope(Dispatchers.IO).launch {
myFun()
}
As far as what I've tried to make it work I've tried closing the coroutine context and it didn't work. I'm assuming Flows work differently than regular coroutines.
Update:
I'm listening through Firebase using this block of code. I don't know if it'll help but maybe the way I implemented it is causing the issue?
inline fun <reified T> Query.listen(): Flow<T?>? =
callbackFlow {
val valueListener = object : ValueEventListener {
override fun onCancelled(databaseError: DatabaseError) {
close()
}
override fun onDataChange(dataSnapshot: DataSnapshot) {
try {
val value = dataSnapshot.getValue(T::class.java)
offer(value)
} catch (exp: Exception) {
if (!isClosedForSend) offer(null)
}
}
}
addValueEventListener(valueListener)
awaitClose { removeEventListener(valueListener) }
}
collect is a suspending function, the code after collect will only run once the flow completes.
Launch it in a separate coroutine:
private suspend fun myFun() {
coroutineScope {
launch {
viewmodel.getListener()?.collect { myClass->
//do something here
}
}
withContext(Dispatchers.Main) { updateUI() } //the code never reaches this part
}
}
I forgot to post my own answer to this. I've found the problem before. It's because I wasn't returning the Coroutine Context.
My code has been updated since but with the code above as an example it should be written as follows:
private suspend fun myFun() {
viewmodel.getListener()?.collect { myClass->
//do something here
return#collect
}
withContext(Dispatchers.Main) { return#withContext updateUI() }
//the code should flow downwards as usual
}

CoroutineScope extension function in a different class

I'm trying to use an extension function to CoroutineScope to launch some asynchronous work.
I'm not sure how to call this method from my main class, see below:
class MyService {
fun CoroutineScope.getFoo() = async(IO|Single|Default) { ... }
}
class MyProgram(val service : MyService) : CoroutineScope {
fun main() {
launch {
// Doesn't work, unresloved `service.getFoo`.
val deferred = service.getFoo() getFoo
// Works, but looks a bit odd IMO.
val deferred = with(service) { getFoo() }
deferred.await()
}
}
}
I know I could just move the async {} keyword to my main method, but in this way, the caller would have to decide the scheduler.
The service knows the nature of its work (IO/Computation bound single-threaded?, etc) and I think it should be the one deciding the scheduler.
As far as I understand your intent is to let the service specify the scheduler. Why not split the specification of the scheduler and the decision to run asynchronously?
Let the service function be suspendable and use withContext to specify the scheduler.
And let the caller decide, if the function should run asynchronously.
class MyService {
suspend fun getFoo() = withContext(Dispatchers.IO) {
//work
}
}
abstract class MyProgram(val service: MyService) : CoroutineScope {
fun main() {
launch {
val deferred = async { service.getFoo() }
//some work
deferred.await()
}
}
}
Why not make getFoo a normal function and pass in the scope:
fun getFoo(scope: CoroutineScope) = scope.async {
//work }
}
launch {
service.getFoo(this)
}

How to launch a Kotlin coroutine in a `suspend fun` that uses the current parent Scope?

How can I launch a coroutine from a suspend function and have it use the current Scope? (so that the Scope doesn't end until the launched coroutine also ends)
I'd like to write something like the following –
import kotlinx.coroutines.*
fun main() = runBlocking { // this: CoroutineScope
go()
}
suspend fun go() {
launch {
println("go!")
}
}
But this has a syntax error: "Unresolved Reference: launch". It seems launch must be run in one of the following ways –
GlobalScope.launch {
println("Go!")
}
Or
runBlocking {
launch {
println("Go!")
}
}
Or
withContext(Dispatchers.Default) {
launch {
println("Go!")
}
}
Or
coroutineScope {
launch {
println("Go!")
}
}
None of these alternatives does what I need. Either the code "blocks" instead of "spawning", or it spawns but the parent scope won't wait for its completion before the parent scope itself ends.
I need it to "spawn" (launch) in the current parent coroutine scope, and that parent scope should wait for the spawned coroutine to finish before it ends itself.
I expected that a simple launch inside a suspend fun would be valid and use its parent scope.
I'm using Kotlin 1.3 and cotlinx-coroutines-core:1.0.1.
You should make the function go an extension function of CoroutineScope:
fun main() = runBlocking {
go()
go()
go()
println("End")
}
fun CoroutineScope.go() = launch {
println("go!")
}
Read this article to understand why it is not a good idea to start in a suspend functions other coroutines without creating a new coroutineScope{}.
The convention is: In a suspend functions call other suspend functions and create a new CoroutineScope, if you need to start parallel coroutines. The result is, that the coroutine will only return, when all newly started coroutines have finished (structured concurrency).
On the other side, if you need to start new coroutines without knowing the scope, You create an extensions function of CoroutineScope, which itself it not suspendable. Now the caller can decide which scope should be used.
I believe I found a solution, which is with(CoroutineScope(coroutineContext). The following example illustrates this –
import kotlinx.coroutines.*
fun main() = runBlocking {
go()
go()
go()
println("End")
}
suspend fun go() {
// GlobalScope.launch { // spawns, but doesn't use parent scope
// runBlocking { // blocks
// withContext(Dispatchers.Default) { // blocks
// coroutineScope { // blocks
with(CoroutineScope(coroutineContext)) { // spawns and uses parent scope!
launch {
delay(2000L)
println("Go!")
}
}
}
However, Rene posted a much better solution above.
Say you are dealing with some RxJava Observable and it isn't the time to refactor them, you can now get a hold of a suspend function's CoroutineScope this way:
suspend fun yourExtraordinarySuspendFunction() = coroutineScope {
val innerScope = this // i.e. coroutineScope
legacyRxJavaUggh.subscribe { somePayloadFromRxJava ->
innerScope.launch {
// TODO your extraordinary work
}
}
}

Cannot execute kotlin coroutine (no such method exception

I took this simple code snippet from kotlin examples:
fun main(args: Array<String>) = runBlocking<Unit> {
withTimeout(1300L) {
repeat(1000) { i ->
println("I'm sleeping $i ...")
delay(500L)
}
}
}
When I try to run it, it throws
java.lang.NoSuchMethodError: kotlinx.coroutines.experimental.ScheduledKt.withTimeout$default(JLjava/util/concurrent/TimeUnit;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Ljava/lang/Object;
I use kotlinVersion = '1.1.51'
Any advice?
In your case, make sure that in your build.gradle file you enable coroutines :
kotlin {
experimental {
coroutines "enable"
}
}
Also you should trigger your coroutine with launch(UI) and call explicitly wait() and the suspended methods. The rest of your code will be executed on the UI thread.
Hope it resolved your issue!