I feel like I'm not understanding something about RxJava2's error-handling. (btw, I have read RxJava2 observable take throws UndeliverableException and I understand what happens, but not exactly why or if there's a better way to deal with it than what I discuss below. I have also seen RuntimeException thrown and not caught in RxJava2 Single after it has been disposed and it doesn't seem exactly relevant.)
Let's say I have the following, very much simplified example:
val sub = Observable.fromCallable(callableThatCanReturnNull)
.subscribe({ println(it) }, { System.err.println(it) })
And the following sequence of events happens, in order:
sub.dispose()
- That darn
Callable
returns null.
Looking at the RxJava2 source, I see this:
@Override
public void subscribeActual(Observer<? super T> s) {
DeferredScalarDisposable<T> d = new DeferredScalarDisposable<T>(s);
s.onSubscribe(d);
if (d.isDisposed()) {
return;
}
T value;
try {
value = ObjectHelper.requireNonNull(callable.call(), "Callable returned null");
} catch (Throwable e) {
Exceptions.throwIfFatal(e);
if (!d.isDisposed()) {
s.onError(e);
} else {
RxJavaPlugins.onError(e);
}
return;
}
d.complete(value);
}
Apparently sometime between the first d.isDisposed()
check and the second, d
is disposed, and so we hit RxJavaPlugins.onError(e)
with e
being a NullPointerException
(from ObjectHelper.requireNonNull()
). If we have not called RxJavaPlugins.setErrorHandler()
with something, then the default behavior is to throw a fatal exception that will crash the application. This seems Bad (tm). My current approach is the following:
GlobalErrorFilter errorFilter = new GlobalErrorFilter(BuildConfig.DEBUG /* alwaysCrash */);
RxJavaPlugins.setErrorHandler(t -> {
if (errorFilter.filter(t)) {
// Crash in some cases
throw new RuntimeException(t);
} else {
// Just log it in others
Logger.e("RxError", t, "Ignoring uncaught Rx exception: %s", t.getLocalizedMessage());
}
});
And my GlobalErrorFilter
takes into consideration this fromCallable
case and also UndeliverableException
. In fact, it looks like this:
class GlobalErrorFilter(private val alwaysCrash: Boolean = false) {
/**
* Returns `true` if app should crash; `false` otherwise. Prefers to return `true`.
*/
fun filter(t: Throwable) = when {
alwaysCrash -> true
t is UndeliverableException -> false
t.localizedMessage.contains("Callable returned null") -> false
else -> true
}
}
This feels really hackish. What I want is to tell RxJava that if I have disposed of an observable, I do not care whether (1) it emits new items (2) it completes (3) it onErrors. I just don't care. Is there a way to do that without this global error handler?