0

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?

Tobias Marschall
  • 2,355
  • 3
  • 22
  • 40

2 Answers2

2

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.

Lucas Milotich
  • 370
  • 3
  • 15
  • hey @NathanFallet it could lead to memory leaks if you don't handle properly your resources inside the bloc, maybe you're not closing some connections or you have some pointer to some structure that is not being cleaned. This code itself indeed is not generating a memory leak. – Lucas Milotich Jun 11 '23 at 10:53
0

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")
}
Aleksei Tirman
  • 4,658
  • 1
  • 5
  • 24