Proper way to call a suspended function in ktor - kotlin-coroutines

i would like to start a process is active while my ktor server is up.
suspend fun doWork(){
while(true){
delay(2000L)
printit("I did work")
}
}
I have read that best practice is to avoid runBlocking and GlobalScope.
so i think i should create a custom scope for the coroutine to run in.
(the job is using some db calls so i did this)
val scope = CoroutineScope(Job() + Dispatchers.IO)
i then call it from my main function
fun main() {
val runningJob = scope.launch{
doWork()
}
embeddedServer(Netty, port = 8080, host = "127.0.0.1") {
routing {
get("/") {
call.respondHtml(HttpStatusCode.OK, HTML::index)
}
static("/static") {
resources()
}
}
}.start(wait = true)
}
Is this the proper way to do this? Most of the coroutine examples kind of leave out calling from non suspend functions.
Thank you in advance :)

Related

Kotlin coroutines within a Spring REST endpoint

Given a REST endpoint and two asynchronous coroutines each returning an integer, I want this endpoint to return their sum. The two functions (funA and funB) should run in parallel, in such a way that the whole computation should take ~3secs.
I'm using SpringBoot 2.6.3 and Kotlin Coroutines 1.6.0. Here is my attempt:
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
#RestController
class Controllers {
#GetMapping(
value = ["/summing"]
)
fun summing(): String {
val finalResult = runBlocking {
val result = async { sum() }
println("Your result: ${result.await()}")
return#runBlocking result
}
println("Final Result: $finalResult")
return "$finalResult"
}
suspend fun funA(): Int {
delay(3000)
return 10
}
suspend fun funB(): Int {
delay(2000)
return 90
}
fun sum() = runBlocking {
val resultSum = async { funA().await() + funB().await() }
return#runBlocking resultSum
}
}
The problem is that this code does not compile as await() is not recognised as a valid method. If I remove await() the two functions are executed in series (total time ~5 secs) and instead of the expected result (100), I get:
Your result: DeferredCoroutine{Completed}#1c1fe804
Final Result: DeferredCoroutine{Completed}#48a622de
and so the endpoint returns "DeferredCoroutine{Completed}#48a622de".
I want the endpoint to return "100" instead and within ~3secs. How can I achieve this?
You really messed this up ;-) There are several problems with your code:
Use runBlocking() only to use another runBlocking() inside it.
Use async() and immediately call await() on it (in summing()) - it does nothing.
funA().await() and funB().await() don't make any sense really. These functions return integers, you can't await() on already acquired integers.
Generally, using much more code than needed.
The solution is pretty simple: use runBlocking() once to jump into coroutine world and then use async() to start both functions concurrently to each other:
runBlocking {
val a = async { funA() }
val b = async { funB() }
a.await() + b.await()
}
Or alternatively:
runBlocking {
listOf(
async { funA() },
async { funB() },
).awaitAll().sum()
}
Or (a little shorter, but I consider this less readable):
runBlocking {
val a = async { funA() }
funB() + a.await()
}
Also,runBlocking() is not ideal. I believe Spring has support for coroutines, so it would be better to make summing() function suspend and use coroutineScope() instead of runBlocking() - this way the code won't block any threads.

My MapsActivity crashes without any error message

I think that the problem is I don't know to use well the Coroutines. In Maps Activity you'll see that I access to a PointsDao suspend function that returns a List of objects that I want to use to create marks at my Google Maps Activity.
#AndroidEntryPoint
class MapsActivity : AppCompatActivity(), OnMapReadyCallback {
private lateinit var mMap: GoogleMap
private lateinit var binding: ActivityMapsBinding
private lateinit var requestPermissionLauncher: ActivityResultLauncher<Array<String>>
private val permissions = arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION)
private lateinit var fusedLocationClient: FusedLocationProviderClient
private val mapsViewModel: MapsViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMapsBinding.inflate(layoutInflater)
setContentView(binding.root)
requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) {
permissions ->
if (permissions.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false)) {
Log.d("fine_location", "Permission granted")
} else {
Log.d("fine_location", "Permission not granted")
getBackToMainActivity()
Toast.makeText(this, "Necessites acceptar els permisos de geolocalització per a realitzar la ruta", Toast.LENGTH_LONG).show()
}
if (permissions.getOrDefault(Manifest.permission.ACCESS_COARSE_LOCATION, false)) {
Log.d("coarse_location", "Permission granted")
} else {
Log.d("coarse_location", "Permission not granted")
getBackToMainActivity()
Toast.makeText(this, "Necessites acceptar els permisos de geolocalització per a realitzar la ruta", Toast.LENGTH_LONG).show()
}
}
// Obtain the SupportMapFragment and get notified when the map is ready to be used.
val mapFragment = supportFragmentManager
.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync(this)
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
requestLocationPermissions()
}
/**
* Manipulates the map once available.
* This callback is triggered when the map is ready to be used.
* This is where we can add markers or lines, add listeners or move the camera.
*/
override fun onMapReady(googleMap: GoogleMap) {
mMap = googleMap
CoroutineScope(Dispatchers.Main).launch {
val listOfPoints = getRoutePoints()
for (point in listOfPoints) {
mMap.addMarker(MarkerOptions().position(LatLng( point.latitude, point.longitude)))
if (point == listOfPoints[0]) {
mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(LatLng(point.latitude, point.longitude), 18f))
}
}
}
}
private fun requestLocationPermissions() {
when (PackageManager.PERMISSION_GRANTED) {
ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) -> {
Log.d("fine_location", "Permission already granted")
}
ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) -> {
Log.d("coarse_location", "Permission already granted")
}
else -> {
requestPermissionLauncher.launch(permissions)
}
}
}
private fun getBackToMainActivity() {
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
}
private fun getRouteId(): Int {
return intent.getIntExtra("routeId", 0)
}
// Gets the points from room repository through ViewModel
private fun getRoutePoints(): List<PointOfInterest> {
val route = getRouteId()
var points = emptyList<PointOfInterest>()
CoroutineScope(Dispatchers.IO).launch {
points = mapsViewModel.getRoutePoints(route)
}
return points
}
This is my ViewModel for this Activity:
#HiltViewModel
class MapsViewModel #Inject constructor(private val repository: RoomRepository): ViewModel() {
suspend fun getRoutePoints(routeId: Int): List<PointOfInterest> {
return repository.getPointsByRouteId(routeId)
}
}
And the Dao:
#Dao
interface PointsDao
{
#Query("SELECT * FROM points_tbl WHERE route_id = :routeId")
suspend fun getRoutePoints(routeId: Int): List<PointOfInterest>
}
My stracktrace error:
Process: com.buigues.ortola.touristics, PID: 27515
java.lang.IllegalStateException: Method addObserver must be called on the main thread
at androidx.lifecycle.LifecycleRegistry.enforceMainThreadIfNeeded(LifecycleRegistry.java:317)
at androidx.lifecycle.LifecycleRegistry.addObserver(LifecycleRegistry.java:172)
at androidx.lifecycle.SavedStateHandleController.attachToLifecycle(SavedStateHandleController.java:49)
at androidx.lifecycle.SavedStateHandleController.create(SavedStateHandleController.java:70)
at androidx.lifecycle.AbstractSavedStateViewModelFactory.create(AbstractSavedStateViewModelFactory.java:67)
at androidx.lifecycle.AbstractSavedStateViewModelFactory.create(AbstractSavedStateViewModelFactory.java:84)
at dagger.hilt.android.internal.lifecycle.HiltViewModelFactory.create(HiltViewModelFactory.java:109)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.kt:171)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.kt:139)
at androidx.lifecycle.ViewModelLazy.getValue(ViewModelLazy.kt:44)
at androidx.lifecycle.ViewModelLazy.getValue(ViewModelLazy.kt:31)
at com.buigues.ortola.touristics.ui.MapsActivity.getMapsViewModel(MapsActivity.kt:39)
at com.buigues.ortola.touristics.ui.MapsActivity.getRoutePoints(MapsActivity.kt:123)
at com.buigues.ortola.touristics.ui.MapsActivity.access$getRoutePoints(MapsActivity.kt:31)
at com.buigues.ortola.touristics.ui.MapsActivity$onMapReady$1.invokeSuspend(MapsActivity.kt:85)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
The problem is here in getRoutePoints().
CoroutineScope(Dispatchers.IO).launch {
points = mapsViewModel.getRoutePoints(route)
}
The by viewModels() in your ViewModel property does a lazy load of the ViewModel. As a result, if you access your ViewModel property for the first time when you are not on the main thread, it will try to create it on the wrong thread, triggering this crash. ViewModels must be constructed on the main thread.
CoroutineScope(Dispatchers.IO) means you are creating a coroutine scope that by default uses background IO threads, so this code is run on a background thread.
You should not be creating a CoroutineScope for this anyway, because your Activity already has one that is properly managed by the Activity lifecycle (so it will cancel any in-progress jobs if the activity is closed, to avoid wasting resources).
Also, getRoutePoints() is a suspend function. There's no reason for you to be using Dispatchers.IO here. A suspend function by convention is safe to call from any dispatcher. (It is however possible to write one that breaks convention, but Room is properly designed and does not break convention.)
To fix the crash and run a coroutine properly, you should use lifecycleScope.launch { //.... However, this function as you have designed it won't do what you expect. It launches a coroutine to retrieve a value, but then it immediately returns before that coroutine has finished running, so in this case will just return the initial emptyList(). When you launch a coroutine, you are queuing up background work, but the current function that called launch continues synchronously without waiting for the coroutine results. If it did, it would be a blocking function. There's more information about that in my answer here.
So, you should instead make this a suspend function:
// Gets the points from room repository through ViewModel
private suspend fun getRoutePoints(): List<PointOfInterest> {
val route = getRouteId()
return mapsViewModel.getRoutePoints(route)
}
And your onMapReady function should also be fixed to use proper scope:
override fun onMapReady(googleMap: GoogleMap) {
mMap = googleMap
lifecycleScope.launch {
val listOfPoints = getRoutePoints()
for (point in listOfPoints) {
mMap.addMarker(MarkerOptions().position(LatLng( point.latitude, point.longitude)))
if (point == listOfPoints[0]) {
mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(LatLng(point.latitude, point.longitude), 18f))
}
}
}
}

Kotlin Coroutines validate running on same Dispatcher

I have a custom Scope that is using a single thread as it's Dispatcher.
private val jsDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
private val jsScope = CoroutineScope(jsDispatcher + SupervisorJob() + CoroutineName("JS-Thread"))
Let's assume I have a code block that uses the above scope to launch a new coroutine and call multiple suspend methods
jsScope.launch {
sampleMethod()
sampleMethod2()
sampleMethod3()
}
I need to validate and throw an exception if one of the above sample methods is not running on the above JS thread
private suspend fun sampleMethod() = coroutineScope {
//Implement me
validateThread()
}
How can this be enforced?
You can check the current thread name in your method:
private suspend fun sampleMethod() = coroutineScope {
assert(Thread.currentThread().name == "js-thread") // Doesn't work!
}
However, newSingleThreadExecutor uses DefaultThreadFactory which produces thread names like pool-N-thread-M which cannot really be validated because you don't know M or N. I see two solutions here:
Take advantage of the fact that you have a single thread and change its name as soon as you create the executor:
runBlocking {
jsScope.launch {
Thread.currentThread().name = "js-thread"
}
}
Pass a custom thread factory: Executors.newSingleThreadExecutor(MyThreadFactory("js-thread"))
private class MyThreadFactory(private val name: String) : ThreadFactory {
private val group: ThreadGroup
private val threadNumber = AtomicInteger(1)
init {
val s = System.getSecurityManager()
group = if (s != null) {
s.threadGroup
} else {
Thread.currentThread().threadGroup
}
}
override fun newThread(r: Runnable): Thread {
val t = Thread(group, r, "$name-${threadNumber.getAndIncrement()}", 0)
if (t.isDaemon) {
t.isDaemon = false
}
if (t.priority != Thread.NORM_PRIORITY) {
t.priority = Thread.NORM_PRIORITY
}
return t
}
}
Code was adapted from DefaultThreadFactory. Guava and apache-commons also provide utility methods to do the same. This has the advantage that it works for any thread pool, not just single-threaded.
After some research, I took a look at the withContext() implementation and the answer to my question was right there.
Taken from the withContext() implementation, this is how to check if current coroutine context is on same dispatcher as other context/scope
if (newContext[ContinuationInterceptor] === oldContext[ContinuationInterceptor]) {
// same dispatcher
}

How to build a monitor with infinite loop?

I am building a monitor in Kotlin to schedule certain operations, what I want is a program that inserts or updates some database entries for a given time intervall. What I got so far is a program that runs for a given time span, but I have an infinite loop in my porgram that takes up to 30% of processor power when it is not time for an update. So my question is how to build a monitor without an infinite loop?
this my code so far:
while(!operations.done && appConfigurations.run_with_monitor) {
if (DataSourceMonitor.isReadyForUpdate(lastMonitorModel)) {
operations.update()
}
}
operations is an entire sequence of different actions to execute. Each operation implementing the IScheduler interface.
interface IScheduler {
var done: Boolean
fun update()
fun reset()
}
Example of implementation:
class Repeat(private val task: IScheduler) : IScheduler {
override var done = false
override fun update() {
if (this.task.done) {
this.reset()
}
this.task.update()
//logger.info { "Update repeat, done is always $done" }
}
override fun reset() {
this.task.reset()
this.done = false
}
}
class Sequence(private val task1: IScheduler, private val task2: IScheduler): IScheduler {
override var done = false
var current = task1
var next = task2
override fun update() {
if (!this.done) {
this.current.update()
if (this.current.done) {
this.current = this.next
}
if (this.next.done) {
this.done = true
}
}
}
class Print(private val msg: String): IScheduler {
override var done = false
override fun update() {
println(this.msg)
this.done = true
}
override fun reset() {
this.done = false
}
}
The value of operations can be as follows:
val operations = Repeat(Sequence(Print("First action"), Print("Another action")))
**So right now my monitor is working and completely functional, but how can I improve the performance of the infinite loop? **
Hope anyone has some ideas about this.
If your DataSourceMonitor has no way to block until isReadyForUpdate is going to return true, then the usual approach is to add a delay. eg:
while(!operations.done && appConfigurations.run_with_monitor) {
if (DataSourceMonitor.isReadyForUpdate(lastMonitorModel)) {
operations.update()
} else {
Thread.sleep(POLL_DELAY);
}
}
If it's always ready for update there won't be any delay, but if it ever isn't ready for update then it'll sleep. You'll need to tune the POLL_DELAY. Bigger values mean less CPU usage, but greater latency in detecting new events to process. Smaller values produce less latency, but use more CPU.
If you really want to get fancy you can have the poll delay start small and then increase up to some maximum, dropping back down once events are found. This is probably overkill, but look up "adaptive polling" if you're interested.
I have refactored my code and I can accomplish the same result with less code, by removing the IScheduler interface by the abstract class TimerTask. The job can be done with these lines of code:
val scheduler = Sequence(
Print("Executed task 1"),
Sequence(Print("Executed task 2"),
Sequence(Print("Executed task 3"), Print("Finished Scheduler")))
)
Timer().schedule(scheduler, DELAY, PERIOD)
All the interface implementations are changed to TimerTask implementations:
class Print(private val msg: String): TimerTask() {
override fun run() {
println(msg)
}
}
class Sequence(private val task1: Runnable, private val task2: Runnable): TimerTask() {
override fun run() {
task1.run()
task2.run()
}
}

Dont understand how to make flux subscription working in kotlin

I'm new to reactive programming. I expect to see
test provider started
Beat 1000
Beat 2000
in logs but there is only test provider started and no Beat or on complete messages. Looks like I miss something
#Service
class ProviderService {
#PostConstruct
fun start(){
val hb: Flux<HeartBeat> = Flux.interval(Duration.ofSeconds(1)).map { HeartBeat(it) }
val provider = Provider("test", hb)
}
}
////////////////////////
open class Provider(name: String, heartBests: Flux<HeartBeat>) {
companion object {
val log = LoggerFactory.getLogger(Provider::class.java)!!
}
init {
log.info("$name provider started")
heartBests.doOnComplete { log.info("on complete") }
heartBests.doOnEach { onBeat(it.get().number) }
}
fun onBeat(n: Number){
log.info("Beat $n")
}
}
/////
class HeartBeat(val number: Number)
three pretty common mistakes here:
operators like doOnEach return a new Flux instance with the added behavior, so you need to (re)assign to a variable or use a fluent style
nothing happens until you subscribe() (or a variant of it. blockXXX do also subscribe under the hood for instance...)
such a pipeline is fully asynchronous, and runs on a separate Thread due to the time dimension of the source, interval. As a result, control would immediately return in init even if you had subscribed, potentially causing the main thread and then the app to exit.
In your code lambda from 'doOnComplete' has been never called, because you created infinite stream. Method 'doOnEach' as 'map' is intermediate operations (like map in streams), its doesn't make a call.
And you have another mistake, reactive suggests "fluent pattern".
Try this simple example:
import reactor.core.publisher.Flux
import java.time.Duration
fun main(args: Array<String>) {
val flux = Flux.interval(Duration.ofSeconds(1)).map { HeartBeat(it) }
println("start")
flux.take(3)
.doOnEach { println("on each $it") }
.map { println("before map");HeartBeat(it.value * 2) }
.doOnNext { println("on next $it") }
.doOnComplete { println("on complete") }
.subscribe { println("subscribe $it") }
Thread.sleep(5000)
}
data class HeartBeat(val value: Long)

Resources