Why does this coroutine crash at run time? - kotlin

I am trying to make sure my app responds appropriate in the event of a backend failure, I am using realm/mongo to create an async task that fetches the user.
I have these two blocks:
override suspend fun logIn(accessToken: String) {
val user = logInInternal(accessToken)
realmAsyncOpen(user)
}
and
private suspend fun logInInternal(accessToken: String) = suspendCancellableCoroutine<User> { continuation ->
val customJWTCredentials: Credentials = Credentials.jwt(accessToken)
app.loginAsync(customJWTCredentials) {
if (it.isSuccess) {
continuation.resumeWith(Result.success(app.currentUser()!!))
} else {
continuation.resumeWithException(RealmLoginException().initCause(it.error))
}
}
}
logInInternal crashes when I hit the resumeWithException part. I have also tried using app.login(credentials) since the method is suspending, without luck there. Why does my app crash when I resume with exception?
I am causing the call to 502 out when hit.

The docs of resumeWithException say:
Resumes the execution of the corresponding coroutine so that the exception is re-thrown right after the last suspension point.
Which means you need to catch that exception:
override suspend fun logIn(accessToken: String) {
try {
val user = logInInternal(accessToken)
realmAsyncOpen(user)
} catch(e: RealmLoginException /*or e: Exception - to catch all exceptions*/) {
// handle exception
}
}

Related

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()
}

How can I run Kotlin suspend fun on #ReactMethod (ReactNative)

I need to implement a custom gRPC on Kotlin native side.
#ReactMethod can't be suspend func.
How can I run it?
#ReactMethod
fun connect(ipAddress: String, port: Int) {
try {
channel = ManagedChannelBuilder.forAddress(ipAddress, port).usePlaintext().build()
var guidKey = Metadata.Key.of("GUID", Metadata.ASCII_STRING_MARSHALLER)
metadata.put(guidKey, GUID)
val stub = DBServiceGrpcKt.DBServiceCoroutineStub(channel!!)
var request = GrpcDBService.SignInRequest.newBuilder()
.setUserName("user")
.setPassword("11111")
.build()
try {
//******* this part *****
suspend fun coroutine() {
var response = stub.trySignIn(request,metadata)
}
} catch (e: Exception) {
Log.d("grpcConnect", e.localizedMessage)
}
} catch (e: Error) {
Log.d("grpcConnect ", e.localizedMessage)
}
finally {
channel?.shutdown()
}
}
You need to create a coroutine, for example, define a scope (somewhere in your class) and use it with launch:
myPluginScope.launch {
val response = stub.trySignIn(request,metadata)
// Return, the result
}
Creating the scope is easy, the tricky part is to find where to cancel it. Check the documentation on React native modules to find a good place to call cancel on your scope:
val myPluginScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
Now, is important to decide whether the connect function should behave as an asynchronous function or not. For example, if the sign in operation takes time or uses the network, connect should probably return the result through a Promise or Callback, so the Javascript side is not blocked:
#ReactMethod
fun connect(ipAddress: String, port: Int, promise: Promise) {
myPluginScope.launch {
try {
// Make the sign in happen in a separate thread:
val response = withContext(context = Dispatchers.IO) {
stub.trySignIn(request, metadata)
}
// Resolve the promise in the calling thread (The UI thread)
promise.resolve(response.hypotheticalCode)
} catch (e: Exception) {
promise.reject("Sign in error!", e)
}
}
}
See:
https://reactnative.dev/docs/native-modules-android#promises
https://kotlinlang.org/docs/async-programming.html#coroutines

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.

Gather multiple async results in Kotlin Coroutines ignoring the exceptions with timeouts

I have a Generator class that, basically, generates some data, like:
interface Generator {
suspend fun generate(): String?
}
There are multiple implementations. Some of them may throw exceptions and some of them may took too long to generate the data:
class Faulty : Generator {
override suspend fun generate(): String? {
println("Faulty")
throw IllegalArgumentException();
}
}
class Lingering : Generator {
override suspend fun generate(): String? {
println("Lingering")
delay(Duration.ofHours(1))
return null
}
}
But some implementations are worthy
class Good : Generator {
override suspend fun generate(): String {
println("Good")
return "Goooood"
}
}
What I need to do is to gather the data generated by a list of pre-configured generators, giving each of them a timeout for its generate and ignoring the exceptions (but logging them):
fun main() = runBlocking {
val generators = listOf(Faulty(), Lingering(), Good())
val results = supervisorScope {
generators
.map { generator ->
async(CoroutineExceptionHandler { context, exception ->
println(exception)
}) {
withTimeoutOrNull(5000) {
generator.generate()
}
}
}
.awaitAll()
.filterNotNull()
}
println(results)
}
The problem is that this code fails with exception:
Faulty
Lingering
Good
Exception in thread "main" java.lang.IllegalArgumentException
at Faulty.generate (File.kt:12)
at FileKt$main$1$results$1$1$2$1.invokeSuspend (File.kt:41)
at FileKt$main$1$results$1$1$2$1.invoke (File.kt:-1)
Why doesn't the supervisorScope catch it? What am I doing wrong?
From the documentation of CoroutineExceptionHandler:
An optional element in the coroutine context to handle uncaught exceptions.
and
A coroutine that was created using async always catches all its exceptions and represents them in the resulting Deferred object, so it cannot result in uncaught exceptions.
so it follows that your async job doesn't emit an uncaught exception. The exception is rethrown by the awaitAll() call that happens later. You have put your uncaught exception handler only within your async context, so it will not be used.
Furthermore, children coroutines do not emit uncaught exceptions anyway. Their exceptions are delegated up to their root ancestor.
As explained here in the last section titled Exceptions in supervised coroutines, children of a supervisor scope must have a root coroutine that uses the handler.
What you can do is wrap the whole task in a launch block that uses the handler. For some reason it doesn't work to install the handler on runBlocking. Maybe that doesn't count as a root job?
fun main() = runBlocking{
val job = GlobalScope.launch(CoroutineExceptionHandler { context, exception ->
println(exception)
}) {
val generators = listOf(Faulty(), Lingering(), Good())
val results =
supervisorScope {
generators
.map { generator ->
async {
withTimeoutOrNull(5000) {
generator.generate()
}
}
}
.awaitAll()
.filterNotNull()
}
println(results)
}
job.join()
}
But I think maybe the only reason you introduced the CoroutineExceptionHandler was for ignoring exceptions. That strategy won't work, because the handler only deals with uncaught exceptions, meaning it's too late to recover. The job has already failed at that point. You will have to wrap your generate() call within the async block in a try/catch or runCatching.

JobCancellationException StandaloneCoroutine was cancelled

Since we are using Coroutines (1.3.5 used) we have a lot of crash : JobCancellationException - StandaloneCoroutine was cancelled.
I read a lot of thread about theses problems and I tried a lot of solution in production but crashes always occurs.
In all our viewmodels we are using the viewmodelscope so it's ok.
But in our data layer we need to launch a tracking events which are fire and forget task. In first step we used a GlobalScope.launch. I was thinking the CancelletationException was due to this global scope so I removed it and create an extension in the data layer with using a SupervisorJob and a CoroutineExceptionHandler:
private val appScope = CoroutineScope(Dispatchers.Default + SupervisorJob())
private val coroutineExceptionHandler by lazy { CoroutineExceptionHandler { _, throwable -> logw("Error occurred inside Coroutine.", throwable) } }
fun launchOnApp(block: suspend CoroutineScope.() -> Unit) {
appScope.launch(coroutineExceptionHandler) { block() }
}
But I always saw crashes with this code. Do I need to use cancelAndJoin method? Which strategy I can use with a clean archi and this kind of work please?
Thanks in advance
You can build an extension utility that catches the cancellation exception, and do what you want with it:
fun CoroutineScope.safeLaunch(block: suspend CoroutineScope.() -> Unit): Job {
return this.launch {
try {
block()
} catch (ce: CancellationException) {
// You can ignore or log this exception
} catch (e: Exception) {
// Here it's better to at least log the exception
Log.e("TAG","Coroutine error", e)
}
}
}
And you can use the extension with a coroutine scope of your choice, for example the global scope:
GlobalScope.safeLaunch{
// here goes my suspend functions and stuff
}
or any viewmodel scope:
myViewModel.viewModelScope.safeLaunch{
// here goes my suspend functions and stuff
}
I recommend not to use GlobalScope for the following reasons:
This is the description in CoroutineScope.kt :
This is a delicate API. It is easy to accidentally create resource or memory leaks when GlobalScope is used. A coroutine launched in GlobalScope is not subject to the principle of structured concurrency, so if it hangs or gets delayed due to a problem (e.g. due to a slow network), it will stay working and consuming resources.
There are limited circumstances under which GlobalScope can be legitimately and safely used, such as top-level background processes that must stay active for the whole duration of the application's lifetime. Because of that, any use of GlobalScope requires an explicit opt-in with #OptIn(DelicateCoroutinesApi::class)
// A global coroutine to log statistics every second, must be always active
#OptIn(DelicateCoroutinesApi::class)
val globalScopeReporter = GlobalScope.launch {
while (true) {
delay(1000)
logStatistics()
}
}
If you don't mind the job being canceled you can just ignore it.
To manage tasks that have been canceled or should not be undone, you need to know where your code is coming from and improve it.
var job: Job? = null
fun requestJob(from:String) {
Log.send("test : from = $from")
if (job != null) {
job?.cancel()
Log.d("test", "test : canceled")
}
job = GlobalScope.launch {
(0..10).forEach { i ->
delay(1000)
Log.d("test", "test : job $i")
}
}.apply {
invokeOnCompletion {
Log.d("test", "test : from = $from, reason = ${it?.message ?: "completed"}")
job = null
}
}
}