Skip to content

Commit d32ad88

Browse files
printStackTrace and getCause implementation
The printStackTrace and getCause implementations have different approaches to handling circular references. If on a JVM where printStackTrace is called directly, the full causal chain will always be printed. If on one such as Dalvik where getCause is invoked directly then it will cut the causal chain if it sees a duplicate.
1 parent f3e4658 commit d32ad88

File tree

2 files changed

+154
-20
lines changed

2 files changed

+154
-20
lines changed

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

Lines changed: 82 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.ArrayList;
2121
import java.util.Collection;
2222
import java.util.Collections;
23+
import java.util.HashSet;
2324
import java.util.LinkedHashSet;
2425
import java.util.List;
2526
import java.util.Set;
@@ -31,6 +32,12 @@
3132
*
3233
* Its invariant is to contains an immutable, ordered (by insertion order), unique list of non-composite exceptions.
3334
* This list may be queried by {@code #getExceptions()}
35+
*
36+
* The `printStackTrace()` implementation does custom handling of the StackTrace instead of using `getCause()` so it
37+
* can avoid circular references.
38+
*
39+
* If `getCause()` is invoked, it will lazily create the causal chain but stop if it finds any Throwable in the chain
40+
* that it has already seen.
3441
*/
3542
public final class CompositeException extends RuntimeException {
3643

@@ -42,7 +49,7 @@ public final class CompositeException extends RuntimeException {
4249
public CompositeException(String messagePrefix, Collection<? extends Throwable> errors) {
4350
Set<Throwable> deDupedExceptions = new LinkedHashSet<Throwable>();
4451
List<Throwable> _exceptions = new ArrayList<Throwable>();
45-
for (Throwable ex: errors) {
52+
for (Throwable ex : errors) {
4653
if (ex instanceof CompositeException) {
4754
deDupedExceptions.addAll(((CompositeException) ex).getExceptions());
4855
} else {
@@ -52,7 +59,7 @@ public CompositeException(String messagePrefix, Collection<? extends Throwable>
5259

5360
_exceptions.addAll(deDupedExceptions);
5461
this.exceptions = Collections.unmodifiableList(_exceptions);
55-
this.message = exceptions.size() + " exceptions occurred. See them in causal chain below.";
62+
this.message = exceptions.size() + " exceptions occurred. ";
5663
}
5764

5865
public CompositeException(Collection<? extends Throwable> errors) {
@@ -62,8 +69,7 @@ public CompositeException(Collection<? extends Throwable> errors) {
6269
/**
6370
* Retrieves the list of exceptions that make up the {@code CompositeException}
6471
*
65-
* @return the exceptions that make up the {@code CompositeException}, as a {@link List} of
66-
* {@link Throwable}s
72+
* @return the exceptions that make up the {@code CompositeException}, as a {@link List} of {@link Throwable}s
6773
*/
6874
public List<Throwable> getExceptions() {
6975
return exceptions;
@@ -74,9 +80,47 @@ public String getMessage() {
7480
return message;
7581
}
7682

83+
private Throwable cause = null;
84+
7785
@Override
7886
public synchronized Throwable getCause() {
79-
return null;
87+
if (cause == null) {
88+
// we lazily generate this causal chain if this is called
89+
CompositeExceptionCausalChain _cause = new CompositeExceptionCausalChain();
90+
Set<Throwable> seenCauses = new HashSet<Throwable>();
91+
92+
Throwable chain = _cause;
93+
for (Throwable e : exceptions) {
94+
if (seenCauses.contains(e)) {
95+
// already seen this outer Throwable so skip
96+
continue;
97+
}
98+
seenCauses.add(e);
99+
100+
List<Throwable> listOfCauses = getListOfCauses(e);
101+
// check if any of them have been seen before
102+
for(Throwable child : listOfCauses) {
103+
if (seenCauses.contains(child)) {
104+
// already seen this outer Throwable so skip
105+
e = new RuntimeException("Duplicate found in causal chain so cropping to prevent loop ...");
106+
continue;
107+
}
108+
seenCauses.add(child);
109+
}
110+
111+
// we now have 'e' as the last in the chain
112+
try {
113+
chain.initCause(e);
114+
} catch (Throwable t) {
115+
// ignore
116+
// the javadocs say that some Throwables (depending on how they're made) will never
117+
// let me call initCause without blowing up even if it returns null
118+
}
119+
chain = chain.getCause();
120+
}
121+
cause = _cause;
122+
}
123+
return cause;
80124
}
81125

82126
/**
@@ -106,16 +150,18 @@ public void printStackTrace(PrintWriter s) {
106150
/**
107151
* Special handling for printing out a CompositeException
108152
* Loop through all inner exceptions and print them out
109-
* @param s stream to print to
153+
*
154+
* @param s
155+
* stream to print to
110156
*/
111157
private void printStackTrace(PrintStreamOrWriter s) {
112158
StringBuilder bldr = new StringBuilder();
113159
bldr.append(this).append("\n");
114-
for (StackTraceElement myStackElement: getStackTrace()) {
160+
for (StackTraceElement myStackElement : getStackTrace()) {
115161
bldr.append("\tat ").append(myStackElement).append("\n");
116162
}
117163
int i = 1;
118-
for (Throwable ex: exceptions) {
164+
for (Throwable ex : exceptions) {
119165
bldr.append(" ComposedException ").append(i).append(" :").append("\n");
120166
appendStackTrace(bldr, ex, "\t");
121167
i++;
@@ -127,7 +173,7 @@ private void printStackTrace(PrintStreamOrWriter s) {
127173

128174
private void appendStackTrace(StringBuilder bldr, Throwable ex, String prefix) {
129175
bldr.append(prefix).append(ex).append("\n");
130-
for (StackTraceElement stackElement: ex.getStackTrace()) {
176+
for (StackTraceElement stackElement : ex.getStackTrace()) {
131177
bldr.append("\t\tat ").append(stackElement).append("\n");
132178
}
133179
if (ex.getCause() != null) {
@@ -178,4 +224,31 @@ void println(Object o) {
178224
printWriter.println(o);
179225
}
180226
}
227+
228+
/* package-private */final static class CompositeExceptionCausalChain extends RuntimeException {
229+
private static final long serialVersionUID = 3875212506787802066L;
230+
/* package-private */static String MESSAGE = "Chain of Causes for CompositeException In Order Received =>";
231+
232+
@Override
233+
public String getMessage() {
234+
return MESSAGE;
235+
}
236+
}
237+
238+
private final List<Throwable> getListOfCauses(Throwable ex) {
239+
List<Throwable> list = new ArrayList<Throwable>();
240+
Throwable root = ex.getCause();
241+
if (root == null) {
242+
return list;
243+
} else {
244+
while(true) {
245+
list.add(root);
246+
if (root.getCause() == null) {
247+
return list;
248+
} else {
249+
root = root.getCause();
250+
}
251+
}
252+
}
253+
}
181254
}

rxjava-core/src/test/java/rx/exceptions/CompositeExceptionTest.java

Lines changed: 72 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
package rx.exceptions;
1717

1818
import static org.junit.Assert.assertEquals;
19-
20-
import org.junit.Test;
19+
import static org.junit.Assert.assertFalse;
20+
import static org.junit.Assert.assertNotNull;
2121

2222
import java.io.ByteArrayOutputStream;
2323
import java.io.PrintStream;
@@ -26,6 +26,8 @@
2626
import java.util.Arrays;
2727
import java.util.List;
2828

29+
import org.junit.Test;
30+
2931
public class CompositeExceptionTest {
3032

3133
private final Throwable ex1 = new Throwable("Ex1");
@@ -50,37 +52,75 @@ public void testMultipleWithSameCause() {
5052
Throwable e2 = new Throwable("2", rootCause);
5153
Throwable e3 = new Throwable("3", rootCause);
5254
CompositeException ce = new CompositeException("3 failures with same root cause", Arrays.asList(e1, e2, e3));
53-
55+
56+
System.err.println("----------------------------- print composite stacktrace");
57+
ce.printStackTrace();
5458
assertEquals(3, ce.getExceptions().size());
59+
5560
assertNoCircularReferences(ce);
61+
assertNotNull(getRootCause(ce));
62+
System.err.println("----------------------------- print cause stacktrace");
63+
ce.getCause().printStackTrace();
5664
}
5765

5866
@Test(timeout = 1000)
5967
public void testCompositeExceptionFromParentThenChild() {
6068
CompositeException cex = new CompositeException(Arrays.asList(ex1, ex2));
61-
assertNoCircularReferences(cex);
69+
70+
System.err.println("----------------------------- print composite stacktrace");
71+
cex.printStackTrace();
6272
assertEquals(2, cex.getExceptions().size());
73+
74+
assertNoCircularReferences(cex);
75+
assertNotNull(getRootCause(cex));
76+
77+
System.err.println("----------------------------- print cause stacktrace");
78+
cex.getCause().printStackTrace();
6379
}
6480

6581
@Test(timeout = 1000)
6682
public void testCompositeExceptionFromChildThenParent() {
6783
CompositeException cex = new CompositeException(Arrays.asList(ex2, ex1));
68-
assertNoCircularReferences(cex);
84+
85+
System.err.println("----------------------------- print composite stacktrace");
86+
cex.printStackTrace();
6987
assertEquals(2, cex.getExceptions().size());
88+
89+
assertNoCircularReferences(cex);
90+
assertNotNull(getRootCause(cex));
91+
92+
System.err.println("----------------------------- print cause stacktrace");
93+
cex.getCause().printStackTrace();
7094
}
7195

7296
@Test(timeout = 1000)
7397
public void testCompositeExceptionFromChildAndComposite() {
7498
CompositeException cex = new CompositeException(Arrays.asList(ex1, getNewCompositeExceptionWithEx123()));
75-
assertNoCircularReferences(cex);
99+
100+
System.err.println("----------------------------- print composite stacktrace");
101+
cex.printStackTrace();
76102
assertEquals(3, cex.getExceptions().size());
103+
104+
assertNoCircularReferences(cex);
105+
assertNotNull(getRootCause(cex));
106+
107+
System.err.println("----------------------------- print cause stacktrace");
108+
cex.getCause().printStackTrace();
77109
}
78110

79111
@Test(timeout = 1000)
80112
public void testCompositeExceptionFromCompositeAndChild() {
81113
CompositeException cex = new CompositeException(Arrays.asList(getNewCompositeExceptionWithEx123(), ex1));
82-
assertNoCircularReferences(cex);
114+
115+
System.err.println("----------------------------- print composite stacktrace");
116+
cex.printStackTrace();
83117
assertEquals(3, cex.getExceptions().size());
118+
119+
assertNoCircularReferences(cex);
120+
assertNotNull(getRootCause(cex));
121+
122+
System.err.println("----------------------------- print cause stacktrace");
123+
cex.getCause().printStackTrace();
84124
}
85125

86126
@Test(timeout = 1000)
@@ -89,8 +129,16 @@ public void testCompositeExceptionFromTwoDuplicateComposites() {
89129
exs.add(getNewCompositeExceptionWithEx123());
90130
exs.add(getNewCompositeExceptionWithEx123());
91131
CompositeException cex = new CompositeException(exs);
92-
assertNoCircularReferences(cex);
132+
133+
System.err.println("----------------------------- print composite stacktrace");
134+
cex.printStackTrace();
93135
assertEquals(3, cex.getExceptions().size());
136+
137+
assertNoCircularReferences(cex);
138+
assertNotNull(getRootCause(cex));
139+
140+
System.err.println("----------------------------- print cause stacktrace");
141+
cex.getCause().printStackTrace();
94142
}
95143

96144
/**
@@ -101,9 +149,22 @@ private static void assertNoCircularReferences(Throwable ex) {
101149
ByteArrayOutputStream baos = new ByteArrayOutputStream();
102150
PrintStream printStream = new PrintStream(baos);
103151
StringWriter writer = new StringWriter();
152+
ex.printStackTrace(printStream);
153+
assertFalse(baos.toString().contains("CIRCULAR REFERENCE"));
154+
}
104155

105-
ex.printStackTrace();
106-
//ex.printStackTrace(printStream);
107-
//assertFalse(baos.toString().contains("CIRCULAR REFERENCE"));
156+
private static Throwable getRootCause(Throwable ex) {
157+
Throwable root = ex.getCause();
158+
if (root == null) {
159+
return null;
160+
} else {
161+
while(true) {
162+
if (root.getCause() == null) {
163+
return root;
164+
} else {
165+
root = root.getCause();
166+
}
167+
}
168+
}
108169
}
109170
}

0 commit comments

Comments
 (0)