Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Single.blockingGet() -> BlockingMultiObserver/ExceptionCatcher should not wrap Exceptions? #5965

Closed
marcottedan opened this issue Apr 18, 2018 · 9 comments

Comments

@marcottedan
Copy link

Version 2.1.9

Assuming this code:

return Single.error(new IllegalArgumentException("I'm the cause"));

I think there's a issue in the design of ExceptionCatcher.wrapOrThrow. When calling blockingGet(), it wraps any instance of Exception but leaves Runtime and Error as-is. So basically you throw an exception somewhere but the Rx framework bubbles it up with a wrapper you have to unwrap to get your initial cause.

/**
     * If the provided Throwable is an Error this method
     * throws it, otherwise returns a RuntimeException wrapping the error
     * if that error is a checked exception.
     * @param error the error to wrap or throw
     * @return the (wrapped) error
     */
    public static RuntimeException wrapOrThrow(Throwable error) {
        if (error instanceof Error) {
            throw (Error)error;
        }
        if (error instanceof RuntimeException) {
            return (RuntimeException)error;
        }
        return new RuntimeException(error);
    }

It's especially painful in unit tests, where you catch an Exception and have to sometime exception.getCause() and sometime only exception is good enough. So if your Single would be consumed by a Subscriber but you want to test it by blockingGet to ease your life, you end up with an extra coating of exception that won't be there in your real scenario, leading to glue code in tests that don't behave like the original code.

      Single<String> anySingle = Single.error(new IOException("I'm the cause"));
      try {
         anySingle.blockingGet();
      }
      // catch (RuntimeException e) {
      // Should fail, but will pass
      // Assert.assertTrue(e.getCause() instanceof IOException);
      // }
      catch (Exception e) {
         // Should pass, but will fail
         Assert.assertTrue(e instanceof IOException);
      }

The caller code that invokes the ExceptionCatcher is BlockingMultiObserver.

public T blockingGet() {
        if (getCount() != 0) {
            try {
                BlockingHelper.verifyNonBlocking();
                await();
            } catch (InterruptedException ex) {
                dispose();
                throw ExceptionHelper.wrapOrThrow(ex);
            }
        }
        Throwable ex = error;
        if (ex != null) {
            throw ExceptionHelper.wrapOrThrow(ex);
        }
        return value;
    }

I believe that the blockingGet() method should actually throws Exception

Is there any workaround for that?

Thank you very much

@marcottedan
Copy link
Author

Google also deprecated the same exact behavior done here from Guava in Throwables.propagate: https://github.com/google/guava/wiki/Why-we-deprecated-Throwables.propagate

@akarnokd
Copy link
Member

Having throws X where X has to be the widest exception type, Throwable, is equally painful. PR #5951 details the wrapping in these methods.

Is there any workaround for that?

  • Don't use blockingGet for tests, use test() and TestObserver.assertXs.
  • Have your own blocking consumer and declare the signature as you see fit.

Otherwise, the signature is now baked in and can't be changed as it breaks binary compatibility.

@JakeWharton
Copy link
Member

It would only break source compatibility, not binary. I still wouldn't change it though.

@artem-zinnatullin
Copy link
Contributor

Soo, I personally thank everyone (sarcasm) in this issue because you got me to interested and it's 5:34 am…

I've built 0-deps java library that allows throwing checked exceptions without wrapping and without declaring it in method description

https://github.com/artem-zinnatullin/java-uncheck-exceptions

It's not on maven central yet, but jar is available on releases page (or you can build it, see README)

TL;TR: yep, in Java source code you can't throw checked exception without wrapping or declaring it. But in Java bytecode you can, so I do it from bytecode (you can read more in README and check implementation).

If you like it, we can integrate that class into RxJava and maybe Guava later.

@JakeWharton
Copy link
Member

JakeWharton commented Apr 19, 2018 via email

@artem-zinnatullin
Copy link
Contributor

Yep, but it's Lombok we're talking about, I don't know many people who'd like to have it in the project

I should mention it in the readme tho 👍

@JakeWharton
Copy link
Member

JakeWharton commented Apr 19, 2018 via email

@vanniktech
Copy link
Collaborator

vanniktech commented Apr 19, 2018

OkIo is using it https://github.com/square/okio/blob/4ae65b82e301f958cd4f04ba20b964a694267f64/okio/src/main/java/okio/Util.java#L64

public static void sneakyRethrow(Throwable t) {
  Util.<Error>sneakyThrow2(t);
}

@SuppressWarnings("unchecked")
private static <T extends Throwable> void sneakyThrow2(Throwable t) throws T {
  throw (T) t;
}

@artem-zinnatullin
Copy link
Contributor

Yep, Jesse sent it too, what a facepalm then 🤦‍♂️

It was even discussed before in RxJava

Well, that was a fun exercise I guess…

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants