Skip to content

Commit 3f218d4

Browse files
Attach Value Without Wrapping
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.
1 parent ffbf2ff commit 3f218d4

File tree

9 files changed

+78
-11
lines changed

9 files changed

+78
-11
lines changed

rxjava-core/src/main/java/rx/exceptions/Exceptions.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
*/
1616
package rx.exceptions;
1717

18+
import java.util.HashSet;
19+
import java.util.Set;
20+
1821
public class Exceptions {
1922
private Exceptions() {
2023

@@ -53,4 +56,45 @@ else if (t instanceof StackOverflowError) {
5356
throw (LinkageError) t;
5457
}
5558
}
59+
60+
private static final int MAX_DEPTH = 25;
61+
62+
public static void addCause(Throwable e, Throwable cause) {
63+
Set<Throwable> seenCauses = new HashSet<Throwable>();
64+
65+
int i = 0;
66+
while (e.getCause() != null) {
67+
if (i++ >= MAX_DEPTH) {
68+
// stack too deep to associate cause
69+
return;
70+
}
71+
e = e.getCause();
72+
if (seenCauses.contains(e.getCause())) {
73+
break;
74+
} else {
75+
seenCauses.add(e.getCause());
76+
}
77+
}
78+
// we now have 'e' as the last in the chain
79+
try {
80+
e.initCause(cause);
81+
} catch (Throwable t) {
82+
// ignore
83+
// the javadocs say that some Throwables (depending on how they're made) will never
84+
// let me call initCause without blowing up even if it returns null
85+
}
86+
}
87+
88+
public static Throwable getFinalCause(Throwable e) {
89+
int i = 0;
90+
while (e.getCause() != null) {
91+
if (i++ >= MAX_DEPTH) {
92+
// stack too deep to get final cause
93+
return new RuntimeException("Stack too deep to get final cause");
94+
}
95+
e = e.getCause();
96+
}
97+
return e;
98+
}
99+
56100
}

rxjava-core/src/main/java/rx/exceptions/OnErrorThrowable.java

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@ public class OnErrorThrowable extends RuntimeException {
2222
private final boolean hasValue;
2323
private final Object value;
2424

25-
public OnErrorThrowable(Throwable exception) {
25+
private OnErrorThrowable(Throwable exception) {
2626
super(exception);
2727
hasValue = false;
2828
this.value = null;
2929
}
3030

31-
public OnErrorThrowable(Throwable exception, Object value) {
31+
private OnErrorThrowable(Throwable exception, Object value) {
3232
super(exception);
3333
hasValue = true;
3434
this.value = value;
@@ -43,10 +43,32 @@ public boolean isValueNull() {
4343
}
4444

4545
public static OnErrorThrowable from(Throwable t) {
46-
if (t instanceof OnErrorThrowable) {
47-
return (OnErrorThrowable) t;
46+
Throwable cause = Exceptions.getFinalCause(t);
47+
if (cause instanceof OnErrorThrowable.OnNextValue) {
48+
return new OnErrorThrowable(t, ((OnNextValue) cause).getValue());
4849
} else {
4950
return new OnErrorThrowable(t);
5051
}
5152
}
53+
54+
public static Throwable decorate(Throwable e, Object value) {
55+
Exceptions.addCause(e, new OnNextValue(value));
56+
return e;
57+
}
58+
59+
public static class OnNextValue extends RuntimeException {
60+
61+
private static final long serialVersionUID = -3454462756050397899L;
62+
private final Object value;
63+
64+
public OnNextValue(Object value) {
65+
super("OnError while emitting onNext value: " + value);
66+
this.value = value;
67+
}
68+
69+
public Object getValue() {
70+
return value;
71+
}
72+
73+
}
5274
}

rxjava-core/src/main/java/rx/operators/OperatorCast.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import rx.Observable.Operator;
1919
import rx.Subscriber;
20+
import rx.exceptions.Exceptions;
2021
import rx.exceptions.OnErrorThrowable;
2122

2223
/**
@@ -49,7 +50,7 @@ public void onNext(T t) {
4950
try {
5051
o.onNext(castClass.cast(t));
5152
} catch (Throwable e) {
52-
onError(new OnErrorThrowable(e, t));
53+
onError(OnErrorThrowable.decorate(e, t));
5354
}
5455
}
5556
};

rxjava-core/src/main/java/rx/operators/OperatorDoOnEach.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public void onNext(T value) {
6060
try {
6161
doOnEachObserver.onNext(value);
6262
} catch (Throwable e) {
63-
onError(new OnErrorThrowable(e, value));
63+
onError(OnErrorThrowable.decorate(e, value));
6464
return;
6565
}
6666
observer.onNext(value);

rxjava-core/src/main/java/rx/operators/OperatorFilter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public void onNext(T t) {
5454
child.onNext(t);
5555
}
5656
} catch (Throwable e) {
57-
child.onError(new OnErrorThrowable(e, t));
57+
child.onError(OnErrorThrowable.decorate(e, t));
5858
}
5959
}
6060

rxjava-core/src/main/java/rx/operators/OperatorGroupBy.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ public void onNext(T t) {
131131
// we have the correct group so send value to it
132132
gps.onNext(t);
133133
} catch (Throwable e) {
134-
onError(new OnErrorThrowable(e, t));
134+
onError(OnErrorThrowable.decorate(e, t));
135135
}
136136
}
137137

rxjava-core/src/main/java/rx/operators/OperatorMap.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public void onNext(T t) {
5353
try {
5454
o.onNext(transformer.call(t));
5555
} catch (Throwable e) {
56-
onError(new OnErrorThrowable(e, t));
56+
onError(OnErrorThrowable.decorate(e, t));
5757
}
5858
}
5959

rxjava-core/src/main/java/rx/operators/OperatorScan.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ public void onNext(T value) {
9797
try {
9898
this.value = accumulator.call(this.value, value);
9999
} catch (Throwable e) {
100-
observer.onError(new OnErrorThrowable(e, value));
100+
observer.onError(OnErrorThrowable.decorate(e, value));
101101
}
102102
}
103103
observer.onNext(this.value);

rxjava-core/src/main/java/rx/operators/OperatorZip.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ void tick() {
194194
// all have something so emit
195195
observer.onNext(zipFunction.call(vs));
196196
} catch (Throwable e) {
197-
observer.onError(new OnErrorThrowable(e, vs));
197+
observer.onError(OnErrorThrowable.decorate(e, vs));
198198
return;
199199
}
200200
// now remove them

0 commit comments

Comments
 (0)