0

TL;DR

I think that flatMap combined with switchMap might not terminate the stream correctly, thus UndeliverableException happens. How can I fix this?

The Structure

I'm making a little bit complex stream -- combined with flatMap and switchMap -- like below, in RxKotlin (RxJava 3):

someObservable
    .flatMapMaybe {
        if (matchCondition(it)) Maybe.just(it)
        else Maybe.never()
    }.flatMapSingle {
        procedureMiddle(it) // Inconsistent-time-consuming Single
    }.switchMap {
        procedureLater(it)
    }.doOnError {
        dealWithError(e)
    }.retry()
    .subscribeBy(
        // ...
    )

The procedureMiddle inside flatMapSingle has a chance of returning Error in the end.

The Exception

It turns out that sometimes the error from procedureMiddle might jump out of structure, not being ignored by retry, nor dealt in dealWithError in doOnError:

W/System.err: io.reactivex.rxjava3.exceptions.UndeliverableException: The exception could not be delivered to the consumer because it has already canceled/disposed the flow or the exception has nowhere to go to begin with. Further reading: https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#error-handling | my.custom.app.CustomException
// traces indicates that the Exception is thrown from inside procedureMiddle

The Question

In fact the UndeliverableException doesn't really cause crash, but it's a little bit annoying for me --especially it looks like a situation I need to handle. However I thought the structure is correctly written? So here's my question:

  1. Does switchMap really (correctly) terminate the last stream from flatMap? (And can be used to prevent UndeliverableException?)
  2. If so, at which part of code I should adjust? If not so, how could I prevent the exceptions along with my structure? (I want to concat procedureLater after procedureMiddle, and keep only latest one)

Any suggestion or explanation would be helpful.

Samuel T. Chou
  • 521
  • 6
  • 31

1 Answers1

0

The RxJava2 Official Doc explains a few about UndeliverableException:

UndeliverableException: wraps the original exception that can't be delivered due to lifecycle restrictions on a Subscriber / Observer.

So it, actually, does NOT mean that your code have any problems (directly). It just reminds you that some streams still throw error after you ended/canceled/disposed them.

About the question:

Does switchMap really (correctly) terminate the last stream from flatMap? (And can be used to prevent UndeliverableException?)

  • Yes, it terminates the last stream.
  • And NO. UndeliverableException would still happen. Because it terminates the last stream, so if the last stream throws error, the error would be wrapped and thrown as UndeliverableException.

If so, at which part of code I should adjust? If not so, how could I prevent the exceptions along with my structure?

You don't need to adjust the code, nor preventing UndeliverableException, as it always have a chance to come up when disposing/cancelling/ending any streams (manually or even automatically.)

I suggest just ignoring it:

RxJavaPlugins.setErrorHandler {
    if (it is UndeliverableException) {
        // This happens when throwable comes out AFTER subscription is canceled or disposed.
        // Not what we care about as it is likely disposed by user or system.
        Log.d(
            "Application", "UndeliverableException: ignored.\n" +
                    "$it: problem ${it.cause} / ${it.cause?.message}\n" +
                    "at ${it.cause?.stackTraceToString()}"
        )
        return@setErrorHandler
    }
    // other Exceptions: do some report
}
Samuel T. Chou
  • 521
  • 6
  • 31