Skip to content

Commit 8bf8102

Browse files
Merge pull request #1401 from samhendley/onnextvalue
OnError while emitting onNext value: object.toString
2 parents 69b6102 + b50abaa commit 8bf8102

File tree

2 files changed

+127
-1
lines changed

2 files changed

+127
-1
lines changed

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

100644100755
Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ public static class OnNextValue extends RuntimeException {
116116
* the item that the Observable was trying to emit at the time of the exception
117117
*/
118118
public OnNextValue(Object value) {
119-
super("OnError while emitting onNext value: " + value);
119+
super("OnError while emitting onNext value: " + renderValue(value));
120120
this.value = value;
121121
}
122122

@@ -129,5 +129,25 @@ public Object getValue() {
129129
return value;
130130
}
131131

132+
/**
133+
* Render the object if it is a basic type. This avoids the library making potentially expensive
134+
* or calls to toString() which may throw exceptions. See PR #1401 for details.
135+
*
136+
* @param value
137+
* the item that the Observable was trying to emit at the time of the exception
138+
* @return a string version of the object if primitive, otherwise the classname of the object
139+
*/
140+
private static String renderValue(Object value){
141+
if(value == null){
142+
return "null";
143+
}
144+
if(value.getClass().isPrimitive()){
145+
return value.toString();
146+
}
147+
if(value instanceof String){
148+
return (String)value;
149+
}
150+
return value.getClass().getSimpleName() + ".class";
151+
}
132152
}
133153
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package rx.exceptions;
2+
3+
import org.junit.Test;
4+
import rx.Observable;
5+
import rx.Observer;
6+
import rx.functions.Func1;
7+
8+
import java.io.PrintWriter;
9+
import java.io.StringWriter;
10+
11+
import static org.junit.Assert.assertTrue;
12+
import static org.junit.Assert.fail;
13+
14+
/**
15+
* ```java
16+
* public OnNextValue(Object value) {
17+
* super("OnError while emitting onNext value: " + value);
18+
* this.value = value;
19+
* }
20+
* ```
21+
* I know this is probably a helpful error message in some cases but this can be a really costly operation when an objects toString is an expensive call or contains alot of output. I don't think we should be printing this in any case but if so it should be on demand (overload of getMessage()) rather than eagerly.
22+
* <p/>
23+
* In my case it is causing a toString of a large context object that is normally only used for debugging purposes which makes the exception logs hard to use and they are rolling over the log files very quickly.
24+
* <p/>
25+
* There is an added danger that if there is a bug in the toString method it will cause inconsistent exception creation. If the object throws an exception while rendering a string it will actually end up not seeing the real exception.
26+
*/
27+
public final class OnNextValueTest {
28+
private static class BadToString {
29+
30+
private final boolean throwDuringToString;
31+
32+
private BadToString(boolean throwDuringToString) {
33+
this.throwDuringToString = throwDuringToString;
34+
}
35+
36+
@Override
37+
public String toString() {
38+
if (throwDuringToString) {
39+
throw new IllegalArgumentException("Error Making toString");
40+
} else {
41+
return "BadToString";
42+
}
43+
}
44+
}
45+
46+
private static class BadToStringObserver implements Observer<BadToString> {
47+
@Override
48+
public void onCompleted() {
49+
System.out.println("On Complete");
50+
fail("OnComplete shouldn't be reached");
51+
}
52+
53+
@Override
54+
public void onError(Throwable e) {
55+
String trace = stackTraceAsString(e);
56+
System.out.println("On Error: " + trace);
57+
58+
assertTrue(trace, trace.contains("OnNextValue"));
59+
60+
assertTrue("No Cause on throwable" + e, e.getCause() != null);
61+
assertTrue(e.getCause().getClass().getSimpleName() + " no OnNextValue",
62+
e.getCause() instanceof OnErrorThrowable.OnNextValue);
63+
}
64+
65+
@Override
66+
public void onNext(BadToString badToString) {
67+
System.out.println("On Next");
68+
fail("OnNext shouldn't be reached");
69+
70+
}
71+
}
72+
73+
public static String stackTraceAsString(Throwable e) {
74+
StringWriter sw = new StringWriter();
75+
e.printStackTrace(new PrintWriter(sw));
76+
return sw.toString();
77+
}
78+
79+
@Test
80+
public void addOnNextValueExceptionAdded() throws Exception {
81+
Observer<BadToString> observer = new BadToStringObserver();
82+
83+
Observable.from(new BadToString(false))
84+
.map(new Func1<BadToString, BadToString>() {
85+
@Override
86+
public BadToString call(BadToString badToString) {
87+
throw new IllegalArgumentException("Failure while handling");
88+
}
89+
}).subscribe(observer);
90+
91+
}
92+
93+
@Test
94+
public void addOnNextValueExceptionNotAddedWithBadString() throws Exception {
95+
Observer<BadToString> observer = new BadToStringObserver();
96+
97+
Observable.from(new BadToString(true))
98+
.map(new Func1<BadToString, BadToString>() {
99+
@Override
100+
public BadToString call(BadToString badToString) {
101+
throw new IllegalArgumentException("Failure while handling");
102+
}
103+
}).subscribe(observer);
104+
105+
}
106+
}

0 commit comments

Comments
 (0)