Springboot coroutine bean scope or local scope - spring-boot

I have a requirement, where we want to asynchronously handle some upstream request/payload via coroutine. I see that there are several ways to do this, but wondering which is the right approach -
Provide explicit spring service class that implements CoroutineScope
Autowire singleton scope-context backed by certain defined thread-pool dispatcher.
Define method local CoroutineScope object
Following on this question, I'm wondering whats the trade-off if we define method local scopes like below -
fun testSuspensions(count: Int) {
val launchTime = measureTimeMillis {
val parentJob = CoroutineScope(Dispatchers.IO).launch {
repeat(count) {
this.launch {
process() //Some lone running process
}
}
}
}
}
Alternative approach to autowire explicit scope object backed by custom dispatcher -
#KafkaListener(
topics = ["test_topic"],
concurrency = "1",
containerFactory = "someListenerContainerConfig"
)
private fun testKafkaListener(consumerRecord: ConsumerRecord<String, ByteArray>, ack: Acknowledgment) {
try {
this.coroutineScope.launch {
consumeRecordAsync(consumerRecord)
}
} finally {
ack.acknowledge()
}
}
suspend fun consumeRecordAsync(record: ConsumerRecord<String, ByteArray>) {
println("[${Thread.currentThread().name}] Starting to consume record - ${record.key()}")
val statusCode = initiateIO(record) // Add error-handling depending on kafka topic commit semantics.
// Chain any-other business logic (depending on status-code) as suspending functions.
consumeStatusCode(record.key(), statusCode)
}
suspend fun initiateIO(record: ConsumerRecord<String, ByteArray>): Int {
return withContext(Dispatchers.IO) { // Switch context to IO thread for http.
println("[${Thread.currentThread().name}] Executing network call - ${record.key()}")
delay(1000 * 2) // Simulate IO call
200 // Return status-code
}
}
suspend fun consumeStatusCode(recordKey: String, statusCode: Int) {
delay(1000 * 1) // Simulate work.
println("[${Thread.currentThread().name}] consumed record - $recordKey, status-code - $statusCode")
}
Autowiring bean as follows in some upstream config class -
#Bean(name = ["testScope"])
fun defineExtensionScope(): CoroutineScope {
val threadCount: Int = 4
return CoroutineScope(Executors.newFixedThreadPool(threadCount).asCoroutineDispatcher())
}

It depends on what your goal is. If you just want to avoid the thread-per-request model, you can use Spring's support for suspend functions in controllers instead (by using webflux), and that removes the need from even using an external scope at all:
suspend fun testSuspensions(count: Int) {
val execTime = measureTimeMillis {
coroutineScope {
repeat(count) {
launch {
process() // some long running process
}
}
}
}
// all child coroutines are done at this point
}
If you really want your method to return immediately and schedule coroutines that outlive it, you indeed need that extra scope.
Regarding option 1), making custom classes implement CoroutineScope is not encouraged anymore (as far as I understood). It's usually suggested to use composition instead (declare a scope as a property instead of implementing the interface by your own classes). So I would suggest your option 2.
I would say option 3) is out of the question, because there is no point in using CoroutineScope(Dispatchers.IO).launch { ... }. It's no better than using GlobalScope.launch(Dispatchers.IO) { ... } (it has the same pitfalls) - you can read about the pitfalls of GlobalScope in its documentation.
The main problem being that you run your coroutines outside structured concurrency (your running coroutines are not children of a parent job and may accumulate and hold resources if they are not well behaved and you forget about them). In general it's better to define a scope that is cancelled when you no longer need any of the coroutines that are run by it, so you can clean rogue coroutines.
That said, in some circumstances you do need to run coroutines "forever" (for the whole life of your application). In that case it's ok to use GlobalScope, or a custom application-wide scope if you need to customize things like the thread pool or exception handler. But in any case don't create a scope on the spot just to launch a coroutine without keeping a handle to it.
In your case, it seems you have no clear moment when you wouldn't care about the long running coroutines anymore, so you may be ok with the fact that your coroutines can live forever and are never cancelled. In that case, I would suggest a custom application-wide scope that you would wire in your components.

Related

what reason show message that "Possibly blocking call in non-blocking context could lead to thread starvation"?

i using coroutines with spring.
message that "Possibly blocking call in non-blocking context could lead to thread starvation" is show when this code using.
really happen blocking call ?
If happen could you tell me reason ?
Thank you.
suspend fun demo() = withContext(Dispatchers.IO) {
val deferred1 = async {
aRepository.findById("id") // here
}
val deferred2 = async {
bRepository.findById("id") // here
}
deferred1.await()
deferred2.await()
}
I'm assuming that this concerns JPA repositories, and they perform work synchronous, and were build for the servlet architecture, which uses blocking code.
Using them with (asynchronous) coroutines triggers this warning. However, I'm not entirely sure if this really is an issue per se.
A solution to suppress this warning is by wrapping the blocking code with withContext (also the provided fix-it in my intellij), which suspends the coroutine while the code within the block is executed;
suspend fun demo() = withContext(Dispatchers.IO) {
val deferred1 = async {
withContext(Dispatchers.IO) {
aRepository.findById("id") // here
}
}
val deferred2 = async {
withContext(Dispatchers.IO) {
bRepository.findById("id") // here
}
}
deferred1.await()
deferred2.await()
}

Is CoroutineScope(SupervisorJob()) runs in Main scope?

I was doing this code lab
https://developer.android.com/codelabs/android-room-with-a-view-kotlin#13
and having a question
class WordsApplication : Application() {
// No need to cancel this scope as it'll be torn down with the process
val applicationScope = CoroutineScope(SupervisorJob())
// Using by lazy so the database and the repository are only created when they're needed
// rather than when the application starts
val database by lazy { WordRoomDatabase.getDatabase(this, applicationScope) }
val repository by lazy { WordRepository(database.wordDao()) }
}
private class WordDatabaseCallback(
private val scope: CoroutineScope
) : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
INSTANCE?.let { database ->
scope.launch {
var wordDao = database.wordDao()
// Delete all content here.
wordDao.deleteAll()
// Add sample words.
var word = Word("Hello")
wordDao.insert(word)
word = Word("World!")
wordDao.insert(word)
// TODO: Add your own words!
word = Word("TODO!")
wordDao.insert(word)
}
}
}
}
this is the code I found, as you can see, it is directly calling scope.launch(...)
my question is that:
isn't all the Room operations supposed to run in non-UI scope? Could someone help me to understand this? thanks so much!
Is CoroutineScope(SupervisorJob()) runs in Main scope?
No. By default CoroutineScope() uses Dispatchers.Default, as can be found in the documentation:
CoroutineScope() uses Dispatchers.Default for its coroutines.
isn't all the Room operations supposed to run in non-UI scope?
I'm not very familiar specifically with Room, but generally speaking it depends if the operation is suspending or blocking. You can run suspend functions from any dispatcher/thread. deleteAll() and insert() functions in the example are marked as suspend, therefore you can run them from both UI and non-UI threads.

How this piece of code can be improved and rewritten to kotlin coroutines

I'm trying to achieve functionality: I have a rest endpoint that calls code that execution can take a lot of time. My idea to improve experience for now is to wrap that piece of code as a new thread, wait for completion or for some max time to elapse and return an appropriate message. Wrapped code should be completed even through endpoint already send message back. Current implementation looks like this:
private const val N = 1000
private const val MAX_WAIT_TIME = 5000
#RestController
#RequestMapping("/long")
class SomeController(
val service: SomeService,
) {
private val executor = Executors.newFixedThreadPool(N)
#PostMapping
fun longEndpoint(#RequestParam("someParam") someParam: Long): ResponseEntity<String> {
val submit = executor.submit {
service.longExecution(someParam)
}
val start = System.currentTimeMillis()
while (System.currentTimeMillis() - start < MAX_WAIT_TIME) {
if (submit.isDone)
return ResponseEntity.ok("Done")
}
return ResponseEntity.ok("Check later")
}
}
First question is - waiting on while for time seems wrong, we don't release thread, can it be improved?
More important question - how to rewrite it to Kotlin coroutines?
My attempt, simple without returning as soon as task is done, looked like this:
#PostMapping
fun longEndpoint(#RequestParam("someParam") someParam: Long): ResponseEntity<String> = runBlocking {
val result = async {
withContext(Dispatchers.Default) {
service.longExecution(someParam)
}
}
delay(MAX_WAIT_TIME)
return#runBlocking ResponseEntity.ok(if(result.isCompleted) "Done" else "Check later")
}
But even through correct string is returned, answer is not send until longExecution is done. How to fix that, what am I missing? Maybe coroutines are bad application here?
There are several problems with your current coroutines attempt:
you are launching your async computation within runBlocking's scope, so the overall endpoint method will wait for child coroutines to finish, despite your attempt at return-ing before that.
delay() will always wait for MAX_WAIT_TIME even if the task is done quicker than that
(optional) you don't have to use runBlocking at all if your framework supports async controller methods (Spring WebFlux does support suspend functions in controllers)
For the first problem, remember that every time you launch a coroutine that should outlive your function, you have to use an external scope. coroutineScope or runBlocking are not appropriate in these cases because they will wait for your child coroutines to finish.
You can use the CoroutineScope() factory function to create a scope, but you need to think about the lifetime of your coroutine and when you want it cancelled. If the longExecution function has a bug and hangs forever, you don't want to leak the coroutines that call it and blow up your memory, so you should cancel those coroutines somehow. That's why you should store the scope as a variable in your class and cancel it when appropriate (when you want to give up on those operations).
For the second problem, using withTimeout is very common, but it doesn't fit your use case because you want the task to keep going even after you timeout waiting for it. One possible solution would be using select clauses to either wait until the job is done, or wait for some specified maximum time:
// TODO call scope.cancel() somewhere appropriate (when this component is not needed anymore)
val scope = CoroutineScope(Job())
#PostMapping
fun longEndpoint(#RequestParam("someParam") someParam: Long): ResponseEntity<String> {
val job = scope.launch {
longExecution()
}
val resultText = runBlocking {
select {
job.onJoin() { "Done" }
onTimeout(MAX_WAIT_TIME) { "Check later" }
}
}
return ResponseEntity.ok(resultText)
}
Note: I'm using launch instead of async because you don't seem to need the return value of longExecution here.
If you want to solve the problem #3 too, you can simply declare your handler suspend and remove runBlocking around the select:
// TODO call scope.cancel() somewhere appropriate (when this component is not needed anymore)
val scope = CoroutineScope(Job())
#PostMapping
suspend fun longEndpoint(#RequestParam("someParam") someParam: Long): ResponseEntity<String> {
val job = scope.launch {
longExecution()
}
val resultText = select {
job.onJoin() { "Done" }
onTimeout(MAX_WAIT_TIME) { "Check later" }
}
return ResponseEntity.ok(resultText)
}
Note that this requires spring-boot-starter-webflux instead of spring-boot-starter-web.
Your implementation always waits for MAX_WAIT_TIME. This might work:
#PostMapping
fun longEndpoint(#RequestParam("someParam") someParam: Long): ResponseEntity<String> = runBlocking {
try {
withTimeout(MAX_WAIT_TIME) {
async {
withContext(Dispatchers.Default) {
service.longExecution(someParam)
}
}
}
} catch (ex: CancellationException) {
return#runBlocking ResponseEntity.ok("Check later")
}
return#runBlocking ResponseEntity.ok("Done")
}
Although I'm not sure if there will be any unwanted side effects because it seems that this will cancel the coroutine when it reaches MAX_WAIT_TIME. Read more about it here:
Cancellation and timeouts

Returning values into GlobalScope launch using Spring

I have an endpoint exposed, that is launching a coroutine:
val apiCall = ApiCall()
#GetMapping("/example")
fun example(#RequestParam paramExample:String):Int{
GlobalScope.launch{
return apiCall.callApi(paramExample)
}
}
This function is calling another external API, using Retrofit:
suspend fun callApi(param:String):Int{
var tot_records =0
val retrofit: Retrofit = Retrofit.Builder()
.baseUrl(appProperties.sampleUrl)
.addConverterFactory(GsonConverterFactory.create())
.build()
val service = retrofit.create<ResponseService>(ResponseService::class.java)
service.getResponse().enqueue(object : Callback<Response> {
override fun onFailure(call: Call<Response>, throwable: Throwable) {
println("Error")
println(throwable.stackTrace)
}
override fun onResponse(call: Call<Response>, response: Response<Response>) {
println("OK")
println(response.body())
println("Tot records")
tot_records = response.body()?.tot_records!!
}
})
return tot_records
}
The problem is that I can't launch this, the error is: 'return' is not allowed here
Any idea how to fix it and whats is happening?
Thanks for your help
It seems like you can't decide if you want your code to be synchronous (so code waits for its subtasks to finish before continuing) or asynchronous (it launches operations in the background). You intend to return a result from example(), so you need it to be synchronous, but you immediately use launch() to invoke callApi() asynchronously. The same in callApi() - you intend to return from it (so synchronous), but you invoke Retrofit using callbacks (so asynchronous). Note that callApi() has exactly the same problem as example(). Even if it compiles, it still does not really work properly. It always returns 0, because tot_records is returned before being set.
You have to decide between asynchronous and synchronous and stick to it. If you want to go fully asynchronous, then you need to redesign both callApi() and example() to return their results either with callbacks or futures.
However, I suggest going fully synchronous, utilizing Kotlin suspend functions. Make all functions suspend: example(), callApi() (it is already) and ResponseService.getResponse(). The last one will look something like:
suspend fun getResponse(): Response
Then remove GlobalScope.launch(), and almost everything inside enqueue(). Instead, service.getResponse() will return Response object directly, so you can just return its tot_records property.
Also note that in your original code you ignored failures. After above change service.getResponse() will throw exceptions on failures, so you have to handle them.
This solution seems that works:
This is the endpoint declaration:
#GetMapping("/example")
suspend fun example(#RequestParam param:String):CustomResponse{
return coroutineScope {
val job = async{apiCall.callApi(param)}
job.await()
}
}
And this is my function that is calling an external API:
suspend fun callApi(param:String):CustomResponse{
var responseCustom = CustomResponse()
val retrofit: Retrofit = Retrofit.Builder()
.baseUrl(appProperties.reservationUrl)
.addConverterFactory(GsonConverterFactory.create())
.build()
val service = retrofit.create<CustomResponseService>(CustomResponseService::class.java)
responseCustom = service.getResponse(appProperties.token, param).execute().body()!!
return responseCustom
}

Kotlin coroutines running sequentially even with keyword async

Hi guys i'm trying to improve performance of some computation in my system. Basically I want to generate a series of actions based on some data. This doesn't scale well and I want to try doing this in parallel and getting a result after (a bit like how futures work)
I have an interface with a series of implementations that get a collection of actions. And want to call all these in parallel and await the results at the end.
The issue is that, when I view the logs its clearly doing this sequentially and waiting on each action getter before going to the next one. I thought the async would do this asynchronously, but its not.
The method the runBlocking is in, is within a spring transaction. Maybe that has something to do with it.
runBlocking {
val actions = actionsReportGetters.map { actionReportGetter ->
async {
getActions(actionReportGetter, abstractUser)
}
}.awaitAll().flatten()
allActions.addAll(actions)
}
private suspend fun getActions(actionReportGetter: ActionReportGetter, traderUser: TraderUser): List<Action> {
return actionReportGetter.getActions(traderUser)
}
interface ActionReportGetter {
fun getActions(traderUser: TraderUser): List<Action>
}
Looks like you are doing some blocking operation in ActionReportGetter.getActions in a single threaded environment (probably in the main thread).
For such IO operations you should launch your coroutines in Dispatchers.IO which provides a thread pool with multiple threads.
Update your code to this:
async(Dispatchers.IO) { // Switch to IO dispatcher
getActions(actionReportGetter, abstractUser
}
Also getActions need not be a suspending function here. You can remove the suspend modifier from it.

Resources