-
Notifications
You must be signed in to change notification settings - Fork 7.6k
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
onErrorFlatMap + OnErrorThrowable #892
onErrorFlatMap + OnErrorThrowable #892
Conversation
RxJava-pull-requests #823 FAILURE |
These errors are known. Waiting on decision before proceeding. |
Seems like a fairly major breaking change, at least for code that handles errors. I could see the extra info being useful, though, at least for "bug" exceptions you're not expecting. Intentional exceptions should hopefully already be conveying this info. Anyway, seems reasonable as long as this behavior's well documented in every affected operator. Blocking operations that turn |
This change will help address the exact use case mentioned above, when an unexpected/untrapped exception occurs in an observable sequence. Rather than shutdown the sequence, the change will allow the observable to remain 'open'. This is especially useful for 'system-oriented' use cases where the observable should not close even if an unexpected/untrapped error occurs. For example, an observable sequence processing user requests for dispatching, or an observable sequence accepting job requests for scheduling. Just because a request within the observable sequence may fail, for some unexpected reason, the 'system-oriented' sequence should remain available for future requests. Also, for debugging and troubleshooting, having the element/item that caused the exception is critical. |
I found a way to pass the value down the chain without wrapping the Throwable. This way it only shows up if using `onErrorFlatMap` or looking at the final cause on any given Throwable. A final cause will be added so Throwables end up like this: java.lang.RuntimeException: Forced Failure at rx.operators.OperatorMapTest$5.call(OperatorMapTest.java:164) at rx.operators.OperatorMapTest$5.call(OperatorMapTest.java:1) at rx.operators.OperatorMap$1.onNext(OperatorMap.java:54) at rx.operators.OnSubscribeFromIterable.call(OnSubscribeFromIterable.java:43) at rx.operators.OnSubscribeFromIterable.call(OnSubscribeFromIterable.java:1) at rx.Observable$2.call(Observable.java:269) at rx.Observable$2.call(Observable.java:1) at rx.Observable$2.call(Observable.java:269) at rx.Observable$2.call(Observable.java:1) at rx.Observable.subscribe(Observable.java:7022) at rx.Observable.subscribe(Observable.java:6945) at rx.operators.OperatorMapTest.testMapWithError(OperatorMapTest.java:177) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20) at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222) at org.junit.runners.ParentRunner.run(ParentRunner.java:300) at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50) at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197) Caused by: rx.exceptions.OnErrorThrowable$OnNextValue: OnError while emitting onNext value: fail at rx.exceptions.OnErrorThrowable.decorate(OnErrorThrowable.java:55) at rx.operators.OperatorMap$1.onNext(OperatorMap.java:56) ... 33 more Note the final cause: Caused by: rx.exceptions.OnErrorThrowable$OnNextValue: OnError while emitting onNext value: fail Then when onErrorFlatMap is used, it retrieves that final cause out to create an OnErrorThrowable so people don't have to go fetch it from the cause chain.
I have committed a change that allows this to work without wrapping. Instead if adds a final A stack trace would end up with something like this at the end of it:
Any problems with this approach? Is this preferred to wrapping? |
RxJava-pull-requests #831 SUCCESS |
No complains so moving forward with this. Using the causal chain instead of wrapping should be low impact, not intrude on normal uses of Throwables (such as instanceof checks) while still allowing this new behavior and improved stack traces for tracking down what caused the exceptions. |
onErrorFlatMap + OnErrorThrowable
RxJava-pull-requests #832 SUCCESS |
I am working on solving a production error handling use case that needs the ability to handle errors on an
Observable
acting like a message-bus. This requires it to ignore errors if they occur.With @headinthebox the idea of
onErrorFlatMap
evolved, but we now have a decision to make about the implementation.It's pretty easy to solve the first half and allow
onErrorFlatMap
to returnObservables
that are injected into the outputObservable
but don'tonComplete
and thus allow the stream to continue.However, we're looking at whether we can also capture the
T value
that caused the exception to be thrown so we can achieve use cases like this:This outputs:
To accomplish this however we must capture the
value
and wrap theThrowable
in all operators that execute user-provided functions. This results in code like this:instead of this:
The drawback to this is that
onError
will now receive anOnErrorThrowable
if the error comes from a user-provided function. The benefit is that the value associated with the failure is now accessible for debugging, reporting, feedback loops etc.Operators affected by this are
cast
,doOnEach
,filter
,groupBy
,map
,scan
,zip
(and surely others) as these all take user-provided functions.Are there reasons we should not wrap these errors inside an
OnErrorThrowable
?