okhttp: Non-IOException subtypes thrown from interceptor never notify Callback
A programming error in an interceptor or in OkHttp or an error of any kind will bypass the Callback
and crash the Dispatcher
thread leaving your consumer hanging forever. This was reported to Retrofit’s Kotlin coroutine support, but is not specific to it.
My proposal is for adding something like
default void onFailure(Call call, Throwable t) {
if (t instanceof IOException) {
onFailure(call, (IOException) t);
}
}
and deprecating the existing onFailure
callback in a 3.14.3 or 3.15 and the equivalent added to 4.0. This will force Retrofit onto Java 8, although given the added safety I think that’s acceptable.
About this issue
- Original URL
- State: open
- Created 5 years ago
- Reactions: 12
- Comments: 16 (11 by maintainers)
Commits related to this issue
- Cancel calls on unexpected exceptions Closes: https://github.com/square/okhttp/issues/5151 — committed to square/okhttp by swankjesse 5 years ago
- Cancel calls on unexpected exceptions Closes: https://github.com/square/okhttp/issues/5151 — committed to square/okhttp by swankjesse 5 years ago
- Cancel calls on unexpected exceptions Closes: https://github.com/square/okhttp/issues/5151 — committed to square/okhttp by swankjesse 5 years ago
- Cancel calls on unexpected exceptions Closes: https://github.com/square/okhttp/issues/5151 — committed to square/okhttp by swankjesse 5 years ago
- Cancel calls on unexpected exceptions (3.14.x branch) Closes: https://github.com/square/okhttp/issues/5151 — committed to square/okhttp by swankjesse 5 years ago
- Cancel calls on unexpected exceptions (3.12.x branch) Closes: https://github.com/square/okhttp/issues/5151 — committed to square/okhttp by swankjesse 5 years ago
- Cancel calls on unexpected exceptions (3.12.x branch) Closes: https://github.com/square/okhttp/issues/5151 — committed to square/okhttp by swankjesse 5 years ago
- Change interceptor exceptions to subclass IOException instead of RuntimeException. See: https://github.com/square/retrofit/issues/3453 and https://github.com/square/okhttp/issues/5151 — committed to DP-3T/dp3t-sdk-android by M-Wong 3 years ago
+1000 😃 Tried a few times to ask for something allowing handling of non IOException errors and always rejected. Currently using added interceptor for that …
Typical use on Android would be security issues for rooted user who by default block Internet permission, not crashing and having the exception allows to properly notify the user.
I was thinking more like a pluggable handling for the
catch(Throwable)
part with a default implementation.I also liked it more.
the default implementation could just rethrow and an external catch would do exactly what it does now.
as an alternative you could add a new interface and just check for it and use it if available.
I don’t think there’s a clean way to make this kind of change in an API without breaking backward compatibility or requiring an extra step.
@swankjesse if we want to do this, we should do it for 5.x, or close.
cc @JakeWharton
@JakeWharton @swankjesse thanks for answer in https://github.com/square/retrofit/issues/3505
This is currently the implementation in okhttp
the
catch(t: Throwable)
part is what I’m not happy about, but I admit I don’t fully understand it. Like for instance I do not understand why you callcancel()
there and why you can’t instead just wrap the exception into anIOException
and forward it like you do in thecatch (e: IOException)
block.I suppose if you use OkHttp directly it’s not a big issue, you are writing the code for each Http request and can easily wrap/unwrap exception yourself
I think this is more an issue at the Retrofit level than of OkHttp level: wrapping the Interceptor is easier than wrapping a retrofit service that is auto-generated / handled by retrofit.
With RxJava2 retrofit interface:
if an interceptor throw an exception that is not
IOException
theSingle
get canceled and theonFailure
callback ignored.which means that if an Rx observer is waiting for something on it it will never receive a callback and will stay hanging there, the error is not even logged.
Using kotlin coroutines the behavior is different
In this case the coroutines is, again, canceled but the app crashes with the exception
I believe the reason is here:
there no
isActive
check inonFailure
before calling resumeWithException(t) and since the coroutine has been already canceled here it crash the app.In https://github.com/square/okhttp/pull/5457 says
I 100% agree with this.
But I believe that if you call
cancel()
there’s no way for the exception to reach retrofit adapters (and have a chance to be unwrapped)I believe the minimal impact option for a developer right now is to write a custom CallFactory that wraps rebuild an OkHttpClient instance wrapping every interceptor into a Delegate interceptor that just catches and wrap non-IOExceptions, than use a custom CallAdapter to do the unwrapping.
But I believe this is not possible if you use
suspend fun
cause they are build-in into retrofit. So the only other option I could think of is to manually unwrap exceptions above the retrofit service or writing a Proxy, except I’m not sure if there is any implication of writing a Proxy of a Kotlin class.Sorry if I’m wasting your time, just trying to help myself and other developers stumbling into this.
Canceling the call is a very elegant approach. I like it a lot.
Would you put the underlying exception as the cause?