question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Throwing a non-IOException from an OkHttp interceptor cancels a call without an event on the Rx stream

See original GitHub issue

If a non-IOException-derived exception is thrown from an OkHttp interceptor, an RxJava3 call adapter stalls indefinitely and does not signal the exception downstream. (This used to work with the RxJava2 call adapter.)

This is a quick reproducer: https://gist.github.com/realdadfish/c7c0d8cb123c583acd3e683b68afbfa5

The current workaround is to simply make all custom exceptions thrown from within interceptors derive from IOException.

Retrofit: 2.9.0 OkHttp: 4.8.1 RxJava: 3.0.6

Issue Analytics

  • State:open
  • Created 3 years ago
  • Reactions:3
  • Comments:10 (2 by maintainers)

github_iconTop GitHub Comments

1reaction
realdadfishcommented, Aug 27, 2020

Also, I think the real culprit is really okhttp, look at the implementation of RealCall.java:

  @Override protected void execute() {
      boolean signalledCallback = false;
      transmitter.timeoutEnter();
      try {
        Response response = getResponseWithInterceptorChain();
        signalledCallback = true;
        responseCallback.onResponse(RealCall.this, response);
      } catch (IOException e) {
        if (signalledCallback) {
          // Do not signal the callback twice!
          Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
        } else {
          responseCallback.onFailure(RealCall.this, e);
        }
      } catch (Throwable t) {
        cancel();       // <-- this is IMHO too early
        if (!signalledCallback) {
          IOException canceledException = new IOException("canceled due to " + t);
          canceledException.addSuppressed(t);
          responseCallback.onFailure(RealCall.this, canceledException);
        }
        throw t;
      } finally {
        client.dispatcher().finished(this);
      }

and here of the CallEnqueueObservable.java where it exits after it realizes the call is already canceled:

 @Override
    public void onFailure(Call<T> call, Throwable t) {
      if (call.isCanceled()) return;          // <-- yeah, we're canceled and do not process the error, leading to an indefinite open stream

      try {
        observer.onError(t);
      } catch (Throwable inner) {
        Exceptions.throwIfFatal(inner);
        RxJavaPlugins.onError(new CompositeException(t, inner));
      }
    }
1reaction
YaowenGuocommented, Aug 27, 2020

If a non-IOException-derived exception is thrown from an OkHttp interceptor, an RxJava3 call adapter stalls indefinitely and does not signal the exception downstream. (This used to work with the RxJava2 call adapter.)

This is a quick reproducer: https://gist.github.com/realdadfish/c7c0d8cb123c583acd3e683b68afbfa5

The current workaround is to simply make all custom exceptions thrown from within interceptors derive from IOException.

Retrofit: 2.9.0 OkHttp: 4.8.1 RxJava: 3.0.6

@realdadfish

I think this error is not caused by retrofit or OkHttp. I have seen several version OkHttp and Retrofit. They all not catch non-IoException derived exception, Because OkHttp only throw IOException internal. It is cased by RxJava adapter. When use Retrofit.Builder().addCallAdapterFactory(RxJava3CallAdapterFactory.create()) to execute network call. it will call subscribeActual() function in CallEnqueueObservable. It not catch non-IoException derived exception thrown by OkHttp.

  @Override
  protected void subscribeActual(Observer<? super Response<T>> observer) {
    // Since Call is a one-shot type, clone it for each new observer.
    Call<T> call = originalCall.clone();
    CallCallback<T> callback = new CallCallback<>(call, observer);
    observer.onSubscribe(callback);
    if (!callback.isDisposed()) {
      call.enqueue(callback);  // <---------not catch other exception.
    }
  }

You can use RxJava3CallAdapterFactory.createWithScheduler() or RxJava3CallAdapterFactory.createSynchronous() like this.

val retrofit = Retrofit.Builder()
      .client(okhttp)
      .addCallAdapterFactory(RxJava3CallAdapterFactory.createSynchronous())
       // Or 
       // .addCallAdapterFactory(RxJava3CallAdapterFactory.createWithScheduler(Schedulers.io()))
      .baseUrl(server.url("/"))
      .build()

It will use CallExecuteObservable to execute http request. It will catch the non-IoException derived exception and call Rxjava downstream’s onError function.

  protected void subscribeActual(Observer<? super Response<T>> observer) {
   ...
    try {
      Response<T> response = call.execute();
      ...
    } catch (Throwable t) {
      Exceptions.throwIfFatal(t);
      if (terminated) {
        ...
      } else if (!disposable.isDisposed()) {
        try {
          observer.onError(t);
        } catch (Throwable inner) {
          ...
        }
      }
    }
  }

Read more comments on GitHub >

github_iconTop Results From Across the Web

Handle exceptions thrown by a custom okhttp Interceptor in ...
While I was using the Rx implementation, the exception was flawlessly propagated to the onError callback where I was able to handle it...
Read more >
Interceptor - OkHttp
For asynchronous calls made with Call.enqueue, an IOException is propagated to the caller indicating that the call was canceled. The interceptor's exception is ......
Read more >
OKHttp Error Interceptors, RxJava 2, Repository Pattern, Retrofit
In this tutorial series, I'll be explaining the fundamentals of RESTful Web Services, and what the hell that actually means.
Read more >
How to handle RESTful web Services using Retrofit, OkHttp ...
We will use a special OkHttp interceptor class for CoinMarketCap API authentication when making a call to the server.
Read more >
A complete guide to OkHttp - LogRocket Blog
OkHttp provides a nice API via Request.Builder to build requests. Synchronous GET. Making a GET request is as easy as this: OkHttpClient client ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found