Does kotlin coroutines have async call with timer? - kotlin

Does Kotlin has possibility to call function async() in coroutines with some time, witch will return default result after time completion?
I found that it's possible to only call await, and than infinity wait the result.
async {
...
val result = computation.await()
...
}
But real production case than you need to return either default result or exception. What is proper way to do something in Kotlin coroutines? Like something similar to this:
async {
...
val timeout = 100500
val result: SomeDeferredClass = computation.await(timeout)
if (result.isTimeout()) {
// get default value
} else {
// process result
}
...
}

You can use the withTimeout-function. It will throw a CancellationException when it times out. You could catch this exception and return your default value.
Something like this:
async {
...
val timeout = 100500L
try {
withTimeout(timeout) {
computation.await()
}
...
} catch (ex: CancellationException) {
defaultValue
}
}
You could also use the withTimeoutOrNull-function, which returns null on timeout. Like this:
async {
...
val timeout = 100500L
withTimeoutOrNull(timeout) { computation.await() } ?: defaultValue
}
This approach won't let you differentiate between a timeout and a computation that returns null though. The default value would be returned in both cases.
For more info, see here: https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md#timeout

Combining both Extensions and #marstran solution I came to a solution that may fit better to your requirements of having the await function with timeout and default value. Also I think it's a cleaner solution
Just define the extension function:
suspend fun <T> Deferred<T>.await(timeout : Long, defaultValue : T) =
withTimeoutOrNull(timeout) { await() } ?: defaultValue
And you can use it anywhere. Instead of
async {
...
val timeout = 100500L
withTimeoutOrNull(timeout) { computation.await() } ?: defaultValue
}
You can do simply
async {
val timeout = 100500L
computation.await(timeout, defaultValue)
}

Related

How to convert kotlin Result type from List<Result<T>> to Result<List<T>>

I have List<Result<String>> and I would like to convert it to Result<List<String>>. I understand that List<Result<String>> could have both failure and successful results but I would like to terminate in the first failure.
If you want to have a failure as soon there is one Result that is a failure you can do this :
fun <T> List<Result<T>>.toResult() = if (any { it.isFailure }) {
Result.failure<List<Result<Any>>>(Throwable("A result has errors"))
} else {
Result.success(map { it.getOrNull() })
}
With this code, you get a failure as soon as there is one value has a failure.
Or if you don't care handling the error yourself :
fun <T> List<Result<T>>.toResult() = runCatching {
Result.success(map { it.getOrThrow() })
}
In most libraries this function is known as sequence.
Kotlin's Arrow library implements it for its implementation of the type Either, which is a generalization of Result: https://arrow-kt.io/docs/apidocs/arrow-core/arrow.core/sequence.html
With Arrow's Either you would write:
val xs: List<Result<String>> = ...
val ys: Result<List<String>> = xs.sequence()
The Kotlin stdlib does not seem to have it. You could define it as an extension method using getOrThrow, catching any thrown Throwable and wrapping in a Resultagain:
fun <T> List<Result<T>>.sequence(): Result<List<T>> = try {
Result.success(this.map { it.getOrThrow() })
}
catch (e:Throwable) { Result.failure(e) }

Getting data from Datastore for injection

I am trying to retrieve the base url from my proto datastore to be used to initialize my ktor client instance I know how to get the data from the datastore but I don't know how to block execution until that value is received so the client can be initialized with the base url
So my ktor client service asks for a NetworkURLS class which has a method to return the base url
Here is my property to retrieve terminalDetails from my proto datastore
val getTerminalDetails: Flow<TerminalDetails> = cxt.terminalDetails.data
.catch { e ->
if (e is IOException) {
Log.d("Error", e.message.toString())
emit(TerminalDetails.getDefaultInstance())
} else {
throw e
}
}
Normally when I want to get the values I would do something like this
private fun getTerminalDetailsFromStore() {
try {
viewModelScope.launch(Dispatchers.IO) {
localRepository.getTerminalDetails.collect {
_terminalDetails.value = it
}
}
} catch(e: Exception) {
Log.d("AdminSettingsViewModel Error", e.message.toString()) // TODO: Handle Error Properly
}
}
but in my current case what I am looking to do is return terminalDetails.backendHost from a function and that where the issue comes in I know I need to use a coroutine scope to retrieve the value so I don't need to suspend the function but how to a prevent the function returning until the coroutine scope has finished?
I have tried using async and runBlocking but async doesn't work the way I would think it would and runBlocking hangs the entire app
fun backendURL(): String = runBlocking {
var url: String = "localhost"
val job = CoroutineScope(Dispatchers.IO).async {
repo.getTerminalDetails.collect {
it.backendHost
}
}
url
}
Can anyone give me some assistance on getting this to work?
EDIT: Here is my temporary solution, I do not intend on keeping it this way, The issue with runBlocking{} turned out to be the Flow<T> does not finish so runBlocking{} continues to block the app.
fun backendURL(): String {
val details = MutableStateFlow<TerminalDetails>(TerminalDetails.getDefaultInstance())
val job = CoroutineScope(Dispatchers.IO).launch {
repo.getTerminalDetails.collect {
details.value = it
}
}
runBlocking {
delay(250L)
}
return details.value.backendHost
}
EDIT 2: I fully fixed my issue. I created a method with the same name as my val (personal decision) which utilizes runBlocking{} and Flow<T>.first() to block while the value is retrieve. The reason I did not replace my val with the function is there are places where I need the information as well where I can utilize coroutines properly where I am not initializing components on my app
val getTerminalDetails: Flow<TerminalDetails> = cxt.terminalDetails.data
.catch { e ->
if (e is IOException) {
Log.d("Error", e.message.toString())
emit(TerminalDetails.getDefaultInstance())
} else {
throw e
}
}
fun getTerminalDetails(): TerminalDetails = runBlocking {
cxt.terminalDetails.data.first()
}

Kotlin Coroutine/Flow Timeout without cancelling the running coroutine?

I am trying to create a Flow that emits a value after a timeout, without cancelling the underlying coroutine. The idea is that the network call has X time to complete and emit a value and after that timeout has been reached, emit some initial value without cancelling the underlying work (eventually emitting the value from the network call, assuming it succeeds).
Something like this seems like it might work, but it would cancel the underlying coroutine when the timeout is reached. It also doesn't handle emitting some default value on timeout.
val someFlow = MutableStateFlow("someInitialValue")
val deferred = async {
val networkCallValue = someNetworkCall()
someFlow.emit(networkCallValue)
}
withTimeout(SOME_NUMBER_MILLIS) {
deferred.await()
}
I'd like to be able to emit the value returned by the network call at any point, and if the timeout is reached just emit some default value. How would I accomplish this with Flow/Coroutines?
One way to do this is with a simple select clause:
import kotlinx.coroutines.selects.*
val someFlow = MutableStateFlow("someInitialValue")
val deferred = async {
someFlow.value = someNetworkCall()
}
// await the first of the 2 things, without cancelling anything
select<Unit> {
deferred.onAwait {}
onTimeout(SOME_NUMBER_MILLIS) {
someFlow.value = someDefaultValue
}
}
You would have to watch out for race conditions though, if this runs on a multi-threaded dispatcher. If the async finished just after the timeout, there is a chance the default value overwrites the network response.
One way to prevent that, if you know the network can't return the same value as the initial value (and if no other coroutine is changing the state) is with the atomic update method:
val deferred = async {
val networkCallValue = someNetworkCall()
someFlow.update { networkCallValue }
}
// await the first of the 2 things, without cancelling anything
val initialValue = someFlow.value
select<Unit> {
deferred.onAwait {}
onTimeout(300) {
someFlow.update { current ->
if (current == initialValue) {
"someDefaultValue"
} else {
current // don't overwrite the network result
}
}
}
}
If you can't rely on comparisons of the state, you can protect access to the flow with a Mutex and a boolean:
val someFlow = MutableStateFlow("someInitialValue")
val mutex = Mutex()
var networkCallDone = false
val deferred = async {
val networkCallValue = someNetworkCall()
mutex.withLock {
someFlow.value = networkCallValue
networkCallDone = true
}
}
// await the first of the 2 things, without cancelling anything
select<Unit> {
deferred.onAwait {}
onTimeout(300) {
mutex.withLock {
if (!networkCallDone) {
someFlow.value = "someDefaultValue"
}
}
}
}
Probably the easiest way to solve the race condition is to use select() as in #Joffrey's answer. select() guarantees to execute only a single branch.
However, I believe mutating a shared flow concurrently complicates the situation and introduces another race condition that we need to solve. Instead, we can do it really very easily:
flow {
val network = async { someNetworkCall() }
select {
network.onAwait{ emit(it) }
onTimeout(1000) {
emit("initial")
emit(network.await())
}
}
}
There are no race conditions to handle. We have just two simple execution branches, depending on what happened first.
If we need a StateFlow then we can use stateIn() to convert a regular flow. Or we can use a MutableStateFlow as in the question, but mutate it only inside select(), similarly to above:
select {
network.onAwait{ someFlow.value = it }
onTimeout(1000) {
someFlow.value = "initial"
someFlow.value = network.await()
}
}
You can launch two coroutines simultaneously and cancel the Job of the first one, which responsible for emitting default value, in the second one:
val someFlow = MutableStateFlow("someInitialValue")
val firstJob = launch {
delay(SOME_NUMBER_MILLIS)
ensureActive() // Ensures that current Job is active.
someFlow.update {"DefaultValue"}
}
launch {
val networkCallValue = someNetworkCall()
firstJob.cancelAndJoin()
someFlow.update { networkCallValue }
}
You can send the network request and start the timeout delay simultaneously. When the network call succeeds, update the StateFlow with the response. And, when the timeout finishes and we haven't received the response, update the StateFlow with the default value.
val someFlow = MutableStateFlow(initialValue)
suspend fun getData() {
launch {
someFlow.value = someNetworkCall()
}
delay(TIMEOUT_MILLIS)
if(someFlow.value == initialValue)
someFlow.value = defaultValue
}
If the response of the network call can be same as the initialValue, you can create a new Boolean to check the completion of network request. Another option can be to store a reference of the Job returned by launch and check if job.isActive after the timeout.
Edit: In case you want to cancel delay when the network request completes, you can do something like:
val someFlow = MutableStateFlow(initialValue)
suspend fun getData() {
val job = launch {
delay(TIMEOUT_MILLIS)
someFlow.value = defaultValue
}
someFlow.value = someNetworkCall()
job.cancel()
}
And to solve the possible concurrency issue, you can use MutableStateFlow.update for atomic updates.

How to get correct return value for suspend function when using GlobalScope.launch?

I have a suspend function
private suspend fun getResponse(record: String): HashMap<String, String> {}
When I call it in my main function I'm doing this, but the type of response is Job, not HashMap, how can I get the correct return type?
override fun handleRequest(event: SQSEvent?, context: Context?): Void? {
event?.records?.forEach {
try {
val response: Job = GlobalScope.launch {
getResponse(it.body)
}
} catch (ex: Exception) {
logger.error("error message")
}
}
return null
}
Given your answers in the comments, it looks like you're not looking for concurrency here. The best course of action would then be to just make getRequest() a regular function instead of a suspend one.
Assuming you can't change this, you need to call a suspend function from a regular one. To do so, you have several options depending on your use case:
block the current thread while you do your async stuff
make handleRequest a suspend function
make handleRequest take a CoroutineScope to start coroutines with some lifecycle controlled externally, but that means handleRequest will return immediately and the caller has to deal with the running coroutines (please don't use GlobalScope for this, it's a delicate API)
Option 2 and 3 are provided for completeness, but most likely in your context these won't work for you. So you have to block the current thread while handleRequest is running, and you can do that using runBlocking:
override fun handleRequest(event: SQSEvent?, context: Context?): Void? {
runBlocking {
// do your stuff
}
return null
}
Now what to do inside runBlocking depends on what you want to achieve.
if you want to process elements sequentially, simply call getResponse directly inside the loop:
override fun handleRequest(event: SQSEvent?, context: Context?): Void? {
runBlocking {
event?.records?.forEach {
try {
val response = getResponse(it.body)
// do something with the response
} catch (ex: Exception) {
logger.error("error message")
}
}
}
return null
}
If you want to process elements concurrently, but independently, you can use launch and put both getResponse() and the code using the response inside the launch:
override fun handleRequest(event: SQSEvent?, context: Context?): Void? {
runBlocking {
event?.records?.forEach {
launch { // coroutine scope provided by runBlocking
try {
val response = getResponse(it.body)
// do something with the response
} catch (ex: Exception) {
logger.error("error message")
}
}
}
}
return null
}
If you want to get the responses concurrently, but process all responses only when they're all done, you can use map + async:
override fun handleRequest(event: SQSEvent?, context: Context?): Void? {
runBlocking {
val responses = event?.records?.mapNotNull {
async { // coroutine scope provided by runBlocking
try {
getResponse(it.body)
} catch (ex: Exception) {
logger.error("error message")
null // if you want to still handle other responses
// you could also throw an exception otherwise
}
}
}.map { it.await() }
// do something with all responses
}
return null
}
You can use GlobalScope.async() instead of launch() - it returns Deferred, which is a future/promise object. You can then call await() on it to get a result of getResponse().
Just make sure not to do something like: async().await() - it wouldn't make any sense, because it would still run synchronously. If you need to run getResponse() on all event.records in parallel, then you can first go in loop and collect all deffered objects and then await on all of them.

Function returning Deferred with a name that does not end with async

I am trying to learn Kotlin coroutines recently I have noticed that in case of map that returns bunch of async IDE is displaying message saying that Function returning Deferred with a name that does not end with async. This is the code I have
runBlocking {
try {
val siteDeferred = async { getSite(order) }
// Place where I get warning-----------| (Function returning Deferred with a name that does not end with Async)
// v
val orderLineDeferred = order.line.map { async { getOrderDetail(it) } }
// Place where I get warning-------------------| (Function returning Deferred with a name that does not end with Async)
// v
val orderLineProductsDeferred = order.line.map { async { getOrderProductInformation(it.productId) } }
val site = siteDeferred.await()
val orderLine = orderLineDeferred.awaitAll()
val orderLineProducts = orderLineProductsDeferred.awaitAll()
} catch (e: Throwable) {
throw Exception(e.message)
}
}
private suspend getOrderDetail(OrderLine orderLine): OrderDetail...
private suspend getSite(Order order): Site ...
private suspend getOrderProductInformation(String productId): Product ...
Am I missing anything here. Furthermore, I would like to know whether this is the right way to do exception handling or not and is there a way to clean up try block so that I can get the value directly even if that means I will have to use async in other methods.
function getSite() renamed to getSiteAsync(),Others functions are modified in this way.