What's the proper way of returning a result out of a IO coroutine job? - kotlin

The problem is very simple, but I can't really seem to wrap my head around it. I'm launching a non-blocking thread in the IO scope in order to read from a file. However, I can't get the result in time before I return from the method - it always returns the initial empty value "". What am I missing here?
private fun getFileContents(): String {
var result = ""
val fileName = getFilename()
val job = CoroutineScope(Dispatchers.IO).launch {
kotlin.runCatching {
val file = getFile(fileName)
file.openFileInput().use { inputStream ->
result = String(inputStream.readBytes(), Charsets.UTF_8)
}
}
}
return result
}

Coroutines are launched asynchronously. Your non-suspending function cannot wait for the result without blocking. For more information about why asynchronous code results in your function returning with the default result, read the answers here.
getFileContents() has to be a suspend function to be able to return something without blocking, in which case you don't need to launch a coroutine either. But then whatever calls this function must be in a suspend function or coroutine.
private suspend fun getFileContents(): String = withContext(Dispatchers.IO) {
val fileName = getFilename()
kotlin.runCatching {
val file = getFile(fileName)
file.openFileInput().use { inputStream ->
result = String(inputStream.readBytes(), Charsets.UTF_8)
}
}.getOrDefault("")
}
There are two "worlds" of code: either you are in a suspending/coroutine context or you are not. When you are in a function that is not a suspend function, you can only return results that can be computed immediately, or you can block until the result is ready.
Generally, if you're using coroutines, you launch a coroutine at some high level in your code, and then you are free to use suspend functions everywhere because almost all of your code is initially triggered by a coroutine. By "high level", I mean you launch the coroutine when a UI screen appears or a UI button is pressed, for example.
Basically, your coroutine launches are usually in UI listeners and UI event functions, not in lower-level code like the function in your question. The coroutine calls a suspend function, which can call other suspend functions, so you don't need to launch more coroutines to perform your various sequential tasks.
The alternate solution is to return a Deferred with the result, like this:
private fun getFileContents(): Deferred<String> {
val fileName = getFilename()
return CoroutineScope(Dispatchers.IO).async {
kotlin.runCatching {
val file = getFile(fileName)
file.openFileInput().use { inputStream ->
result = String(inputStream.readBytes(), Charsets.UTF_8)
}
}.getOrDefault("")
}
}
But to unpack the result, you will need to call await() on the Deferred instance inside a coroutine somewhere.

Related

How to cancel kotlin coroutine with potentially "un-cancellable" method call inside it?

I have this piece of code:
// this method is used to evaluate the input string, and it returns evaluation result in string format
fun process(input: String): String {
val timeoutMillis = 5000L
val page = browser.newPage()
try {
val result = runBlocking {
withTimeout(timeoutMillis) {
val result = page.evaluate(input).toString()
return#withTimeout result
}
}
return result
} catch (playwrightException: PlaywrightException) {
return "Could not parse template! '${playwrightException.localizedMessage}'"
} catch (timeoutException: TimeoutCancellationException) {
return "Could not parse template! (timeout)"
} finally {
page.close()
}
}
It should throw exception after 5 seconds if the method is taking too long to execute (example: input potentially contains infinite loop) but it doesent (becomes deadlock I assume) coz coroutines should be cooperative. But the method I am calling is from another library and I have no control over its computation (for sticking yield() or smth like it).
So the question is: is it even possible to timeout such coroutine? if yes, then how?
Should I use java thread insted and just kill it after some time?
But the method I am calling is from another library and I have no control over its computation (for sticking yield() or smth like it).
If that is the case, I see mainly 2 situations here:
the library is aware that this is a long-running operation and supports thread interrupts to cancel it. This is the case for Thread.sleep and some I/O operations.
the library function really does block the calling thread for the whole time of the operation, and wasn't designed to handle thread interrupts
Situation 1: the library function is interruptible
If you are lucky enough to be in situation 1, then simply wrap the library's call into a runInterruptible block, and the coroutines library will translate cancellation into thread interruptions:
fun main() {
runBlocking {
val elapsed = measureTimeMillis {
withTimeoutOrNull(100.milliseconds) {
runInterruptible {
interruptibleBlockingCall()
}
}
}
println("Done in ${elapsed}ms")
}
}
private fun interruptibleBlockingCall() {
Thread.sleep(3000)
}
Situation 2: the library function is NOT interruptible
In the more likely situation 2, you're kind of out of luck.
Should I use java thread insted and just kill it after some time?
There is no such thing as "killing a thread" in Java. See Why is Thread.stop deprecated?, or How do you kill a Thread in Java?.
In short, in that case you do not have a choice but to block some thread.
I do not know a solution to this problem that doesn't leak resources. Using an ExecutorService would not help if the task doesn't support thread interrupts - the threads will not die even with shutdownNow() (which uses interrupts).
Of course, the blocked thread doesn't have to be your thread. You can technically launch a separate coroutine on another thread (using another dispatcher if yours is single-threaded), to wrap the libary function call, and then join() the job inside a withTimeout to avoid waiting for it forever. That is however probably bad, because you're basically deferring the problem to whichever scope you use to launch the uncancellable task (this is actually why we can't use a simple withContext here).
If you use GlobalScope or another long-running scope, you effectively leak the hanging coroutine (without knowing for how long).
If you use a more local parent scope, you defer the problem to that scope. This is for instance the case if you use the scope of an enclosing runBlocking (like in your example), which makes this solution pointless:
fun main() {
val elapsed = measureTimeMillis {
doStuff()
}
println("Completely done in ${elapsed}ms")
}
private fun doStuff() {
runBlocking {
val nonCancellableJob = launch(Dispatchers.IO) {
uncancellableBlockingCall()
}
val elapsed = measureTimeMillis {
withTimeoutOrNull(100.milliseconds) {
nonCancellableJob.join()
}
}
println("Done waiting in ${elapsed}ms")
} // /!\ runBlocking will still wait here for the uncancellable child coroutine
}
// Thread.sleep is in fact interruptible but let's assume it's not for the sake of the example
private fun uncancellableBlockingCall() {
Thread.sleep(3000)
}
Outputs something like:
Done waiting in 122ms
Completely done in 3055ms
So the bottom line is either live with this long thing potentially hanging, or ask the developers of that library to handle interruption or make the task cancellable.

Difference between GlobalScope and runBlocking when waiting for multiple async

I have a Kotlin Backend/server API using Ktor, and inside a certain endpoint's service logic I need to concurrently get details for a list of ids and then return it all to the client with the 200 response.
The way I wanted to do it is by using async{} and awaitAll()
However, I can't understand whether I should use runBlocking or GlobalScope.
What is really the difference here?
fun getDetails(): List<Detail> {
val fetched: MutableList<Details> = mutableListOf()
GlobalScope.launch { --> Option 1
runBlocking { ---> Option 2
Dispatchers.IO --> Option 3 (or any other dispatcher ..)
myIds.map { id ->
async {
val providerDetails = getDetails(id)
fetched += providerDetails
}
}.awaitAll()
}
return fetched
}
launch starts a coroutine that runs in parallel with your current code, so fetched would still be empty by the time your getDetails() function returns. The coroutine will continue running and mutating the List that you have passed out of the function while the code that retrieved the list already has the reference back and will be using it, so there's a pretty good chance of triggering a ConcurrentModificationException. Basically, this is not a viable solution at all.
runBlocking runs a coroutine while blocking the thread that called it. The coroutine will be completely finished before the return fetched line, so this will work if you are OK with blocking the calling thread.
Specifying a Dispatcher isn't an alternative to launch or runBlocking. It is an argument that you can add to either to determine the thread pool used for the coroutine and its children. Since you are doing IO and parallel work, you should probably be using runBlocking(Dispatchers.IO).
Your code can be simplified to avoid the extra, unnecessary mutable list:
fun getDetails(): List<Detail> = runBlocking(Dispatchers.IO) {
myIds.map { id ->
async {
getDetails(id)
}
}.awaitAll()
}
Note that this function will rethrow any exceptions thrown by getDetails().
If your project uses coroutines more generally, you probably have higher level coroutines running, in which case this should probably be a suspend function (non-blocking) instead:
suspend fun getDetails(): List<Detail> = withContext(Dispatchers.IO) {
myIds.map { id ->
async {
getDetails(id)
}
}.awaitAll()
}

Why method not return anything after dao method?

My code stop working after call dao method, if I use GlobalScope code working, but then LiveData not update changes
override suspend fun addNewTask(title: String,priority : Int,date: Date): Boolean {
var isSuccess = false
val newTask = hashMapOf(
"title" to title,
"priority" to priority,
"task_points" to converterListOfListToJson(listOf()),
"date" to Timestamp(date)
)
val user: User? = userDao.getCurrentUser()
val newTaskList = user?.tasks?.toMutableList()
val generatedDoc = db.collection(CollectionNames.tasks).document()
val userTasks = db.collection(CollectionNames.users).document(user!!.id)
generatedDoc.set(newTask)
.addOnSuccessListener { isSuccess = true }
.addOnFailureListener { Log.e(TAG, "Error writing document") }.await()
userTasks.update(
"tasks", FieldValue.arrayUnion(generatedDoc.id)
).await()
newTaskList?.add(generatedDoc.id)
taskDao.insert(Task(generatedDoc.id, title, date, listOf(), priority.toLong())) //after code out
user.tasks = newTaskList!!.toList()
userDao.updateUser(user)
return isSuccess
}
This call of this method, addNewTask full complete but after code just end method
fun createTask(title : String, priority : String,date: Date) {
viewModelScope.launch{
tasksUseCase.addNewTask(title, priority, date)
tasksUseCase.updateTaskFromLocalDB().collect { //it is not called
_taskList.postValue(it)
}
}
}
In DAO I just use annotation update and insert
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(task: Task)
My function call in dialog fragment on click, but with viewModel of parent fragment, function deadlock without errors
.addOnSuccessListener { isSuccess = true } will change the variable to true some time in the future, when the background work is done. Your code that uses isSuccess is called before it has a chance to finish the background work.
When you used GlobalScope, it probably used the default dispatcher Dispatchers.Default, so you had a race condition and in some cases it could succeed.
When you used viewModelScope, it uses Dispatchers.Main, so there is no possibility of the result listener being called before the end of this suspend function unless there is some other suspending call in between.
What you need to do to fix it is run your task in a synchronous, suspending way instead of an asynchronous way. Since I don't know what class generatedDoc is, I can't help with that. Many libraries include extension suspend function, usually named await(), that let you get the result synchronously. If they don't provide that, you can write your own using suspendCancellableCoroutine. There are many other questions about that on here that you can search for to see how to use it.
The problem was that the viewModel was a DialogFragment which was destroyed after the method was called, due to which the ViewModelScope also ceased to exist and the method crashed in the middle

Isn't lifecycleScope.launchWhenStarted CoroutineScope available in a functions lambda?

I have following code snippet:
lifecycleScope.launchWhenStarted {
setDiscrepancyChips(basicRegisterItemList = Repository.getTableSet(RegisterType.DISCREPANCY),
chipGroup = fragmentOfflinePhotoCategorySelection.selectionChipsPhotoCategoryDiscrepancyChipGroup,
registrationSetResult = Repository.getRegistrationDiscrepancyIdSet(assignmentId = assignmentId)
){discrepancyId, isChecked ->
Log.i(TAG, "onViewCreated: P103: discrepancyId=$discrepancyId, isChecked = $isChecked")
Repository.updateRegistrationDiscrepancyId(assignmentId = assignmentId, discrepancyId = discrepancyId, isChecked)
}
}
Here my compiler complaints that Repository.updateRegistrationDiscrepancyId isn't inside the coroutine body...
...will this say that a lambda needs to define it's own CoroutineScope like this ?
lifecycleScope.launchWhenStarted {
setDiscrepancyChips(basicRegisterItemList = Repository.getTableSet(RegisterType.DISCREPANCY),
chipGroup = fragmentOfflinePhotoCategorySelection.selectionChipsPhotoCategoryDiscrepancyChipGroup,
registrationSetResult = Repository.getRegistrationDiscrepancyIdSet(assignmentId = assignmentId)
){discrepancyId, isChecked ->
Log.i(TAG, "onViewCreated: P103: discrepancyId=$discrepancyId, isChecked = $isChecked")
lifecycleScope.launchWhenStarted {
Repository.updateRegistrationDiscrepancyId(assignmentId = assignmentId, discrepancyId = discrepancyId, isChecked)
}
}
}
I feel a bit awkward to create two CoroutineScopes within each other, or should I see the lambda as a separate code block which needs to creates its own coroutine scopes.
A lambda essentially is nothing but a function and like any other function in kotlin, a lambda can be marked with suspend modifier.
when lambda is invoked, it starts executing like a normal kotlin function, if its a suspending lambda then it can call other suspending functions but if its not then it cant do that, which is the problem you face.
think of it this way, lets say you have folowing code
lifecycleScope.launchWhenStarted{
someFun()
}
private fun someFun(){
Repository.updateRegistrationDiscrepancyId()
// you get the same error as you do now
}
Your lambda is like someFun, since its not suspending, it cant call suspending functions.
Another option then the one you already used is to mark the lambda parameter in setDiscrepancyChips with suspend modifier. and if setDiscrepancyChips itself is suspending then the lambda will start executing on setDiscrepancyChips's coroutine scope(still depends on how it invokes lambda), otherwise it must start a new coroutine to invoke the lambda

Run code in main thread when IO thread dispatch completes?

I'm working with livedata. I want to run some arbitrary code in IO and then once that has completed, run some arbitrary code in the Main thread.
In JavaScript, you can accomplish something like this by chaining promises together. I know Kotlin is different, but that's at least a framework I'm coming from that I understand.
I have a function that will sometimes be called from Main and sometimes from IO, but it requires no special IO features itself. From within class VM: ViewModel():
private val mState = MyState() // data class w/property `a`
val myLiveData<MyState> = MutableLiveData(mState)
fun setVal(a: MyVal) {
mState = mState.copy(a=a)
myLiveData.value = mState
}
fun buttonClickHandler(a: MyVal) {
setVal(a) // Can execute in Main
}
fun getValFromDb() {
viewModelScope.launch(Dispatchers.IO) {
val a: MyVal = fetchFromDb()
setVal(a) // Error! Cannot call setValue from background thread!
}
}
Seems to me the obvious way would be to execute val a = fetchFromDb() from IO and then pull setVal(a) out of that block and into Main.
Is there a way to accomplish this? I don't see a conceptual reason why this feature could not exist. Is there some idea like
doAsyncThatReturnsValue(Dispatchers.IO) { fetchFromDb()}
.then(previousBlockReturnVal, Dispatchers.Main) { doInMain() }
that could be run in a ViewModel?
Please substitute "coroutine" for "thread" wherever appropriate above. :)
Launch is fine. You just have to switch around the dispatchers and use withContext:
fun getValFromDb() {
// run this coroutine on main thread
viewModelScope.launch(Dispatchers.Main) {
// obtain result by running given block on IO thread
// suspends coroutine until it's ready (without blocking the main thread)
val a: MyVal = withContext(Dispatchers.IO){ fetchFromDb() }
// executed on main thread
setVal(a)
}
}