I'm using RxJava 2 and Retrofit 2 to processing my REST calls. After migration from RxJava 1.x to RxJava 2.x I started getting a lot of unhandled java.net.UnknownHostException exceptions. I was expecting to get onError callback in case of any error during REST call. But seems like not. After deep investigation I found that error handling works a bit different in RxJava 2 (based on this).
Does anybody know if UnknownHostException is not processed by stream and doesn't call onError of the stream? Should UnknownHostException be handled in a way using RxJavaPlugins.setErrorHandler()?
RxJava: 2.1.0
RxAndroid: 2.0.1
Retrofit: 2.2.0
OkHttp: 3.6.0
It depends on what is throwing it. Normally, if there are no other errors around, you should see it via regular onError. The stacktrace should give us a hint.
Are they wrapped with UndeliverableException?
On Mon, Jun 19, 2017, 16:02 David Karnok notifications@github.com wrote:
It depends on what is throwing it. Normally, if there are no other errors
around, you should see it via regular onError. The stacktrace should give
us a hint.—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/ReactiveX/RxJava/issues/5425#issuecomment-309432675,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AA7B3J4sVuR-IaNZrHZuDJDbWB64j1-Mks5sFnFtgaJpZM4N-MTo
.
Here is stacktrace
Fatal Exception: io.reactivex.b.f: java.net.UnknownHostException: Unable to resolve host "example.com": No address associated with hostname
at io.reactivex.plugins.RxJavaPlugins.onError(RxJavaPlugins.java:349)
at io.reactivex.internal.operators.observable.ObservableUnsubscribeOn$UnsubscribeObserver.onError(ObservableUnsubscribeOn.java:67)
at io.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeOnObserver.onError(ObservableSubscribeOn.java:63)
at io.reactivex.internal.operators.observable.ObservableOnErrorNext$OnErrorNextObserver.onError(ObservableOnErrorNext.java:78)
at io.reactivex.internal.disposables.EmptyDisposable.error(EmptyDisposable.java:63)
at io.reactivex.internal.operators.observable.ObservableError.subscribeActual(ObservableError.java:37)
at io.reactivex.Observable.subscribe(Observable.java:10842)
at io.reactivex.internal.operators.observable.ObservableOnErrorNext$OnErrorNextObserver.onError(ObservableOnErrorNext.java:105)
at io.reactivex.internal.operators.observable.ObservableFlatMap$MergeObserver.checkTerminate(ObservableFlatMap.java:495)
at io.reactivex.internal.operators.observable.ObservableFlatMap$MergeObserver.drainLoop(ObservableFlatMap.java:331)
at io.reactivex.internal.operators.observable.ObservableFlatMap$MergeObserver.drain(ObservableFlatMap.java:323)
at io.reactivex.internal.operators.observable.ObservableFlatMap$MergeObserver.onError(ObservableFlatMap.java:288)
at retrofit2.adapter.rxjava2.BodyObservable$BodyObserver.onError(BodyObservable.java:72)
at retrofit2.adapter.rxjava2.CallExecuteObservable.subscribeActual(CallExecuteObservable.java:55)
at io.reactivex.Observable.subscribe(Observable.java:10842)
at retrofit2.adapter.rxjava2.BodyObservable.subscribeActual(BodyObservable.java:34)
at io.reactivex.Observable.subscribe(Observable.java:10842)
at io.reactivex.internal.operators.observable.ObservableFlatMap.subscribeActual(ObservableFlatMap.java:55)
at io.reactivex.Observable.subscribe(Observable.java:10842)
at io.reactivex.internal.operators.observable.ObservableOnErrorNext.subscribeActual(ObservableOnErrorNext.java:38)
at io.reactivex.Observable.subscribe(Observable.java:10842)
at io.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeTask.run(ObservableSubscribeOn.java:96)
at io.reactivex.Scheduler$DisposeTask.run(Scheduler.java:452)
at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:61)
at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:52)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:272)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
at java.lang.Thread.run(Thread.java:761)
Looks like the flow has been disposed just when the error propagates downwards. How does the code look like and what triggers the dispose if any?
Here is the code, but I'm not sure if it's helpful
Observable<UserData> userSettings = refreshUserSettings();
Observable<UserData> userData = refreshUserData();
mSubscription = new SerialSubscription()
mSubscription.set(Observable.concat(userSettings, userData)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SyncSubscriber())));
...
@Override
protected void onDestroy() {
mSubscription.unsubscribe();
super.onDestroy();
}
How does SyncSubscriber works?
Is it possible for you to show refreshUserSettings() and refreshUserData()?
Ok, so after deep debugging session and investigation the source code of RxJava, I discovered that an exception was wrapped with UndeliverableException (even though it's not in stacktrace). So adding RxJavaPlugins.setErrorHandler() solves the problem and handles UnknownHostException wrapped with UndeliverableException. Also, it was nice discovery that first the callback onError of my subscriber is called and only after that I was reaching global error handler. What means exactly that an exception was propagated when the stream was already closed.
Thanks everybody for support! Issue is resolved.
@dkhmelenko Where should i add RxJavaPlugins.setErrorHandler() ?
and Why should i add RxJavaPlugins.setErrorHandler() ?
As to why, the wiki has more information:
https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#error-handling
As to where, typically somewhere at the beginning of the program, before any RxJava calls have been made.
Most helpful comment
As to why, the wiki has more information:
https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#error-handling
As to where, typically somewhere at the beginning of the program, before any RxJava calls have been made.