Hi, can someone explain why exception is not consu...
# coroutines
a
Hi, can someone explain why exception is not consumed by try/catch? Steps to reproduce: fast clicks on button full project
Copy code
class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
  private val singleThread = this + newSingleThreadContext("SingleThread")

  override fun onCreate(savedInstanceState: Bundle?) {
    Timber.plant(Timber.DebugTree())

    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    val api = PokemonApi.createApi()
    button.setOnClickListener {
      singleThread.launch {
        try {
          val names = async { api.getPokemonNames() }
          val types = async { api.getPokemonTypes() }
          val stages = async { api.getPokemonStages() }
          val owners = async { api.getPokemonOwners() }

          names.join()
          types.join()
          stages.join()
          owners.join()
        } catch (e: UnknownHostException) {
          // exception is not consumed
        }
      }
    }

    val crashHandler = Thread.getDefaultUncaughtExceptionHandler()
    val exceptionHandler = Thread.UncaughtExceptionHandler { thread, exception ->
      try {
        Timber.tag("UncaughtException").e(exception)
      } finally {
        crashHandler?.uncaughtException(thread, exception)
      }
    }
    Thread.setDefaultUncaughtExceptionHandler(exceptionHandler)
  }

  interface PokemonApi {
    @GET("/names")
    suspend fun getPokemonNames()

    @GET("/types")
    suspend fun getPokemonTypes()

    @GET("/stages")
    suspend fun getPokemonStages()

    @GET("/owners")
    suspend fun getPokemonOwners()

    companion object {
      fun createApi(): PokemonApi {
        return Retrofit.Builder()
          .baseUrl("<https://unavailable-host.com>")
          .build()
          .create(PokemonApi::class.java)
      }
    }
  }
}
stacktrace
Copy code
E/UncaughtException: <http://java.net|java.net>.UnknownHostException: Unable to resolve host "<http://unavailable-host.com|unavailable-host.com>": No address associated with hostname
        at <http://java.net|java.net>.Inet6AddressImpl.lookupHostByName(Inet6AddressImpl.java:156)
        at <http://java.net|java.net>.Inet6AddressImpl.lookupAllHostAddr(Inet6AddressImpl.java:103)
        at <http://java.net|java.net>.InetAddress.getAllByName(InetAddress.java:1152)
        at okhttp3.Dns$Companion$DnsSystem.lookup(Dns.kt:49)
        at okhttp3.internal.connection.RouteSelector.resetNextInetSocketAddress(RouteSelector.kt:164)
        at okhttp3.internal.connection.RouteSelector.nextProxy(RouteSelector.kt:129)
        at okhttp3.internal.connection.RouteSelector.next(RouteSelector.kt:71)
        at okhttp3.internal.connection.ExchangeFinder.findConnection(ExchangeFinder.kt:199)
        at okhttp3.internal.connection.ExchangeFinder.findHealthyConnection(ExchangeFinder.kt:108)
        at okhttp3.internal.connection.ExchangeFinder.find(ExchangeFinder.kt:76)
        at okhttp3.internal.connection.RealCall.initExchange$okhttp(RealCall.kt:245)
        at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.kt:32)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:100)
        at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.kt:82)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:100)
        at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.kt:83)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:100)
        at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.kt:74)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:100)
        at okhttp3.internal.connection.RealCall.getResponseWithInterceptorChain$okhttp(RealCall.kt:197)
        at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:502)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:919)
    	Suppressed: <http://java.net|java.net>.UnknownHostException: Unable to resolve host "<http://unavailable-host.com|unavailable-host.com>": No address associated with hostname
    		... 24 more
    	Caused by: android.system.GaiException: android_getaddrinfo failed: EAI_NODATA (No address associated with hostname)
        at libcore.io.Linux.android_getaddrinfo(Native Method)
        at libcore.io.ForwardingOs.android_getaddrinfo(ForwardingOs.java:74)
        at libcore.io.BlockGuardOs.android_getaddrinfo(BlockGuardOs.java:200)
        at libcore.io.ForwardingOs.android_getaddrinfo(ForwardingOs.java:74)
        at <http://java.net|java.net>.Inet6AddressImpl.lookupHostByName(Inet6AddressImpl.java:135)
        		... 23 more
     Caused by: android.system.GaiException: android_getaddrinfo failed: EAI_NODATA (No address associated with hostname)
        at libcore.io.Linux.android_getaddrinfo(Native Method)
        at libcore.io.ForwardingOs.android_getaddrinfo(ForwardingOs.java:74)
        at libcore.io.BlockGuardOs.android_getaddrinfo(BlockGuardOs.java:200)
        at libcore.io.ForwardingOs.android_getaddrinfo(ForwardingOs.java:74)
        at <http://java.net|java.net>.Inet6AddressImpl.lookupHostByName(Inet6AddressImpl.java:135)
        	... 23 more
E/AndroidRuntime: FATAL EXCEPTION: SingleThread
    Process: com.example.myapplication, PID: 24936
s
Have you installed a CoroutineException handling in the top-most CoroutineScope? https://medium.com/the-kotlin-chronicle/coroutine-exceptions-3378f51a7d33
đź‘Ž 1
a
Handle do not help me. I think that crash in same reason with https://jakewharton.com/exceptions-and-proxies-and-coroutines-oh-my/
l
Yes I can explain: you need to wrap all your calls to
async
in a local
coroutineScope
, and put your
try
block around it. Without it, the
launch
gets the crash, cancels all children coroutines and crashes its parent scope.
s
But wouldn’t calling
await()
on async calls’
Deferred<T>
results be sufficient?
a
Hm.. it works. One moment i need to check it on real project.
l
@streetsofboston You need to understand structured concurrency. A crashing child coroutine (i.e. unhandled exception that is not a
CancellationException
) will cancel its parent scope. So if you want to handle errors without crashing the parent scope, you need a middle scope (
coroutineScope { ... }
), and catch exceptions/throwables around it. The intent of the behavior is to cancel fast work in the same scope if one fails, to avoid pursuing unneeded work that cannot be used as a part already failed.
s
But installing a
SupervisorJob
MainActivity’s CoroutineScope in the should be sufficient to not cancel the MainActivity’s CoroutineScope after a failure happens. In my opinion, if
names
fails, you want the others (
types
,
stages
, etc) to be cancelled as well.
(And I do understand structure concurrency 🙂 )
a
So... in case like that: I will get error on launch coroute?
Copy code
launch {
  try {
    getCats()
  } catch() {
    // no-op
  }
}

// getCats will throw UnknownHostException
suspend fun callApi() = httpClient.getCats()
l
If it's an Activity, you should use
lifecycleScope
extension brought by AndroidX Lifecycle runtime KTX 2.2.0+ that already uses a
SupervisorJob
for the case where you use independant
async
where one can fail while letting the other still be useful on its own.
đź’Ż 1
a
No its not an activity. its
MainScope() + newSingleThreadContext("SingleThread")
s
Copy code
val names = async { api.getPokemonNames() }
          val types = async { api.getPokemonTypes() }
          val stages = async { api.getPokemonStages() }
          val owners = async { api.getPokemonOwners() }

          val listOfResults = listOf(names, types, stages, owners).awaitAll()
          ... or call `await()` on each one individually depending on your use-case...
Your MainActivity implements a CoroutineScope. But as Louis said, use the
lifecycleScope
instead. It is all set up correctly already (
lifecycleScope.launch { …. }
) 🙂
a
Yeah. All you advice works well with example. Samething another is wrong on real project. Thx.
Oh.. 🤦 As result is it my bad. I forget to add try catch in another place. Place with flow and async works perfectly.
155 Views