JakeWharton / retrofit2-kotlin-coroutines-adapter

A Retrofit 2 adapter for Kotlin coroutine's Deferred type.
Apache License 2.0
1.97k stars 128 forks source link

okhttp interceptor throw SocketTimeoutException caused the crash #41

Closed erichyx closed 5 years ago

erichyx commented 5 years ago

I use retrofit2 and interface return Deferred object, then I got an SocketTimeoutException using the okhttp interceptor, the whole app crashed.

Before using the coroutine, this SocketTimeoutException will not cause app crash, so I don't know how to deal with this problem.

interface api { @GET("info") fun request(): Deferred }

class RequestInterceptor : Interceptor { override fun intercept(chain: Interceptor.Chain): Response? { var request = chain.request() // do something return chain.proceed(request) } }

when I disconnect the network to request and get a retrofit2.httpexception, the app also crashed.

g_82kn4cs1e0o6ml9koqj60

I have used the safeApiCall function to catch exceptions per network request.

suspend fun safeApiCall(call: suspend () -> Response): Response { return try { call() } catch (e: Exception) { Response.error(IOException(e)) } }

noho13 commented 5 years ago

I seem to have a problem in the same area. But have no idea as to what exactly the problem is. Here is my stacktrace. I have also opened an issue in the retrofit project, but this might be more appropriate. Also, I can not reproduce my crash. It only happens sporadically.


com.crashlytics.android.core.CrashlyticsBackgroundWorker.submitAndWait (SourceFile:43)
--
  | com.crashlytics.android.core.CrashlyticsController.handleUncaughtException (SourceFile:285)
  | com.crashlytics.android.core.CrashlyticsController$5.onUncaughtException (SourceFile:269)
  | com.crashlytics.android.core.CrashlyticsUncaughtExceptionHandler.uncaughtException (SourceFile:30)
  | java.lang.ThreadGroup.uncaughtException (ThreadGroup.java:1068)
  | java.lang.ThreadGroup.uncaughtException (ThreadGroup.java:1063)
  | kotlinx.coroutines.CoroutineExceptionHandlerImplKt.handleCoroutineExceptionImpl (SourceFile:35)
  | kotlinx.coroutines.CoroutineExceptionHandlerKt.handleExceptionViaHandler (SourceFile:47)
  | kotlinx.coroutines.CoroutineExceptionHandlerKt.handleCoroutineException (SourceFile:31)
  | kotlinx.coroutines.CoroutineExceptionHandlerKt.handleCoroutineException$default (SourceFile:24)
  | kotlinx.coroutines.AbstractContinuation.handleException (SourceFile:254)
  | kotlinx.coroutines.AbstractContinuation.initParentJobInternal$kotlinx_coroutines_core (SourceFile:206)
  | kotlinx.coroutines.AbstractContinuation.initParentJobInternal$kotlinx_coroutines_core (SourceFile:144)
  | kotlinx.coroutines.ResumeAwaitOnCompletion.invoke (SourceFile:1236)
  | kotlinx.coroutines.JobSupport.notifyCompletion (SourceFile:1351)
  | kotlinx.coroutines.JobSupport.tryFinalizeFinishingState (SourceFile:292)
  | kotlinx.coroutines.JobSupport.tryFinalizeFinishingState (SourceFile:225)
  | kotlinx.coroutines.JobSupport.tryFinalizeFinishingState (SourceFile:792)
  | kotlinx.coroutines.JobSupport.makeCompleting$kotlinx_coroutines_core (SourceFile:715)
  | kotlinx.coroutines.CompletableDeferredImpl.completeExceptionally (SourceFile:72)
  | com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory$BodyCallAdapter$adapt$2.onFailure (SourceFile:97)
  | retrofit2.OkHttpCall$1.callFailure (SourceFile:135)
  | retrofit2.OkHttpCall$1.onFailure (SourceFile:130)
  | okhttp3.RealCall$AsyncCall.execute (SourceFile:215)
  | okhttp3.internal.NamedRunnable.run (SourceFile:32)
  | java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1162)
vitoksmile commented 5 years ago

@erichyx @noho13 If you launch your suspend method with Job, change it to SupervisorJob. Also, catch Throwable instead of Exception.

erichyx commented 5 years ago

@vitoksmile It doesn't work, here is my code snippet.

val parentJob = SupervisorJob() val uiScope = CoroutineScope(Dispatchers.Main + parentJob)

uiScope.launch { safeApiCall { api.getXXX(Id).await() } }

suspend fun safeApiCall(call: suspend () -> Response): Response { return try { call() } catch (e: Throwable) { Response.error(e) } }

engbibi commented 5 years ago

I had the same problem. It seems that upgrading coroutines to version 1.1.1 solved this for me. I had version 1.0.0 before. My code:

private val mJob = SupervisorJob()
private val mScope = CoroutineScope(Dispatchers.Main + mJob)

fun fetchAllData() {
        mScope.launch(Dispatchers.IO) {
            try {
                val fetchResult = configApi.getSettingsAsync()
                val result= fetchResult.await()
                Log.d(TAG, "Received result ${result?.id}")
            } catch (se: SocketTimeoutException) {
                Log.e(TAG, "Error: ${se.message}")
               ...
            } catch (ex: Throwable) {
                Log.e(TAG, "Error ${ex.message}")
               ...
            }
        }
    }

override fun onCleared() {
        super.onCleared()
        mScope.coroutineContext.cancelChildren()        
    }
erichyx commented 5 years ago

@engbibi That's right, thank you!

Studentessa commented 5 years ago

Hello, is there any way to catch the exceptions in any other way? This means i have to add try catch for every call. I don't see this very convenience.

grmaciel commented 5 years ago

Hello, this also happens if you have a CoroutineWorker that calls a Deferred<Response<T>> when the user connection is not stable if crashes when a SocketTimeout is thrown.

Any ideas on this?