Skip to content

Single#retryWhen allows Observable#complete() and throws #3774

Closed
@passsy

Description

@passsy
public void testSingleErrorWithRetry() throws Exception {
    TestSubscriber testSubscriber = new TestSubscriber();

    final RuntimeException myError = new RuntimeException("my error");
    Single.error(myError)
            .retryWhen(errorObservable -> Observable.empty())
            .subscribe(testSubscriber);

    testSubscriber.assertError(myError);
}

This test fails with the following exception:

java.lang.AssertionError: Exceptions differ; expected: java.lang.RuntimeException: my error, actual: java.util.NoSuchElementException: Observable emitted no items

    at rx.observers.TestSubscriber.assertError(TestSubscriber.java:464)
    at com.example.ExampleTest.testSingleErrorWithRetry(ExampleTest.java:77)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    ... 17 more
Caused by: java.util.NoSuchElementException: Observable emitted no items
    at rx.internal.operators.OnSubscribeSingle$1.onCompleted(OnSubscribeSingle.java:59)
    at rx.internal.operators.OnSubscribeRedo$4$1.onCompleted(OnSubscribeRedo.java:326)
    at rx.Observable$EmptyHolder$1.call(Observable.java:1123)
    at rx.Observable$EmptyHolder$1.call(Observable.java:1120)
    at rx.Observable.unsafeSubscribe(Observable.java:8314)
    at rx.internal.operators.OnSubscribeRedo$4.call(OnSubscribeRedo.java:323)
    at rx.schedulers.TrampolineScheduler$InnerCurrentThreadScheduler.enqueue(TrampolineScheduler.java:80)
    at rx.schedulers.TrampolineScheduler$InnerCurrentThreadScheduler.schedule(TrampolineScheduler.java:59)
    at rx.internal.operators.OnSubscribeRedo.call(OnSubscribeRedo.java:320)
    at rx.internal.operators.OnSubscribeRedo.call(OnSubscribeRedo.java:55)
    at rx.Observable.unsafeSubscribe(Observable.java:8314)
    at rx.internal.operators.OnSubscribeSingle.call(OnSubscribeSingle.java:83)
    at rx.internal.operators.OnSubscribeSingle.call(OnSubscribeSingle.java:29)
    at rx.Single$1.call(Single.java:93)
    at rx.Single$1.call(Single.java:73)
    at rx.Single.subscribe(Single.java:1665)
    at com.example.ExampleTest.testSingleErrorWithRetry(ExampleTest.java:67)
    ... 22 more

This is the correct behavior as written in the documentation

If that Observable calls onComplete or onError then retry will call onCompleted or onError on the child subscription.

The child subscription here is the subscriber in OnSubscribeSingle#call() which converts an Observable to a Single. It throws this error in onCompleted.

            @Override
            public void onCompleted() {
                if (emittedTooMany) {
                    // Don't need to do anything here since we already sent an error downstream
                } else {
                    if (itemEmitted) {
                        child.onSuccess(emission);
                    } else {
                        child.onError(new NoSuchElementException("Observable emitted no items"));
                    }
                }
            }

The behavior should be changed. The real child subscription before the Observable -> Single conversion is a SingleSubscriber which doesn't have the method onCompleted(). Better onError is called instead with the latest emitted error instead of the current NoSuchElementException.

The docs should be updated to:

If that Observable calls onComplete or onError then retry will call onError on the child subscription.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions