Skip to content

Commit dd56779

Browse files
Merge 0.20.x into 1.x
ReactiveX#1632 Composite Exception - Circular Reference Handling ReactiveX#1631 Handle Fatal Exceptions in doOnEach
2 parents 0aab682 + 25e1805 commit dd56779

File tree

4 files changed

+312
-111
lines changed

4 files changed

+312
-111
lines changed

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

Lines changed: 174 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -15,54 +15,61 @@
1515
*/
1616
package rx.exceptions;
1717

18+
import java.io.PrintStream;
19+
import java.io.PrintWriter;
1820
import java.util.ArrayList;
1921
import java.util.Collection;
2022
import java.util.Collections;
2123
import java.util.HashSet;
24+
import java.util.LinkedHashSet;
2225
import java.util.List;
2326
import java.util.Set;
2427

2528
/**
26-
* Exception that is a composite of 1 or more other exceptions.
27-
* <p>
28-
* Use <code>getMessage()</code> to retrieve a concatenation of the composite exceptions.
29+
* An Exception that is a composite of one or more other Exceptions. A {@code CompositeException} does not
30+
* modify the structure of any exception it wraps, but at print-time it iterates through the list of
31+
* Throwables contained in the composit in order to print them all.
32+
*
33+
* Its invariant is to contain an immutable, ordered (by insertion order), unique list of non-composite
34+
* exceptions. You can retrieve individual exceptions in this list with {@link #getExceptions()}.
35+
*
36+
* The {@link #printStackTrace()} implementation handles the StackTrace in a customized way instead of using
37+
* {@code getCause()} so that it can avoid circular references.
38+
*
39+
* If you invoke {@link #getCause()}, it will lazily create the causal chain but will stop if it finds any
40+
* Throwable in the chain that it has already seen.
2941
*/
3042
public final class CompositeException extends RuntimeException {
3143

3244
private static final long serialVersionUID = 3026362227162912146L;
3345

3446
private final List<Throwable> exceptions;
3547
private final String message;
36-
private final Throwable cause;
3748

38-
public CompositeException(String messagePrefix, Collection<Throwable> errors) {
49+
public CompositeException(String messagePrefix, Collection<? extends Throwable> errors) {
50+
Set<Throwable> deDupedExceptions = new LinkedHashSet<Throwable>();
3951
List<Throwable> _exceptions = new ArrayList<Throwable>();
40-
CompositeExceptionCausalChain _cause = new CompositeExceptionCausalChain();
41-
int count = errors.size();
42-
errors = removeDuplicatedCauses(errors);
43-
for (Throwable e : errors) {
44-
attachCallingThreadStack(_cause, e);
45-
_exceptions.add(e);
52+
for (Throwable ex : errors) {
53+
if (ex instanceof CompositeException) {
54+
deDupedExceptions.addAll(((CompositeException) ex).getExceptions());
55+
} else {
56+
deDupedExceptions.add(ex);
57+
}
4658
}
59+
60+
_exceptions.addAll(deDupedExceptions);
4761
this.exceptions = Collections.unmodifiableList(_exceptions);
48-
49-
String msg = count + " exceptions occurred. See them in causal chain below.";
50-
if(messagePrefix != null) {
51-
msg = messagePrefix + " " + msg;
52-
}
53-
this.message = msg;
54-
this.cause = _cause;
62+
this.message = exceptions.size() + " exceptions occurred. ";
5563
}
5664

57-
public CompositeException(Collection<Throwable> errors) {
65+
public CompositeException(Collection<? extends Throwable> errors) {
5866
this(null, errors);
5967
}
6068

6169
/**
6270
* Retrieves the list of exceptions that make up the {@code CompositeException}
6371
*
64-
* @return the exceptions that make up the {@code CompositeException}, as a {@link List} of
65-
* {@link Throwable}s
72+
* @return the exceptions that make up the {@code CompositeException}, as a {@link List} of {@link Throwable}s
6673
*/
6774
public List<Throwable> getExceptions() {
6875
return exceptions;
@@ -73,82 +80,176 @@ public String getMessage() {
7380
return message;
7481
}
7582

83+
private Throwable cause = null;
84+
7685
@Override
7786
public synchronized Throwable getCause() {
78-
return cause;
79-
}
80-
81-
private Collection<Throwable> removeDuplicatedCauses(Collection<Throwable> errors) {
82-
Set<Throwable> duplicated = new HashSet<Throwable>();
83-
for (Throwable cause : errors) {
84-
for (Throwable error : errors) {
85-
if(cause == error || duplicated.contains(error)) {
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
8696
continue;
8797
}
88-
while (error.getCause() != null) {
89-
error = error.getCause();
90-
if (error == cause) {
91-
duplicated.add(cause);
92-
break;
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;
93107
}
108+
seenCauses.add(child);
94109
}
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();
95120
}
121+
cause = _cause;
122+
}
123+
return cause;
124+
}
125+
126+
/**
127+
* All of the following {@code printStackTrace} functionality is derived from JDK {@link Throwable}
128+
* {@code printStackTrace}. In particular, the {@code PrintStreamOrWriter} abstraction is copied wholesale.
129+
*
130+
* Changes from the official JDK implementation:<ul>
131+
* <li>no infinite loop detection</li>
132+
* <li>smaller critical section holding {@link PrintStream} lock</li>
133+
* <li>explicit knowledge about the exceptions {@link List} that this loops through</li>
134+
* </ul>
135+
*/
136+
@Override
137+
public void printStackTrace() {
138+
printStackTrace(System.err);
139+
}
140+
141+
@Override
142+
public void printStackTrace(PrintStream s) {
143+
printStackTrace(new WrappedPrintStream(s));
144+
}
145+
146+
@Override
147+
public void printStackTrace(PrintWriter s) {
148+
printStackTrace(new WrappedPrintWriter(s));
149+
}
150+
151+
/**
152+
* Special handling for printing out a {@code CompositeException}.
153+
* Loops through all inner exceptions and prints them out.
154+
*
155+
* @param s
156+
* stream to print to
157+
*/
158+
private void printStackTrace(PrintStreamOrWriter s) {
159+
StringBuilder bldr = new StringBuilder();
160+
bldr.append(this).append("\n");
161+
for (StackTraceElement myStackElement : getStackTrace()) {
162+
bldr.append("\tat ").append(myStackElement).append("\n");
96163
}
97-
if (!duplicated.isEmpty()) {
98-
errors = new ArrayList<Throwable>(errors);
99-
errors.removeAll(duplicated);
164+
int i = 1;
165+
for (Throwable ex : exceptions) {
166+
bldr.append(" ComposedException ").append(i).append(" :").append("\n");
167+
appendStackTrace(bldr, ex, "\t");
168+
i++;
169+
}
170+
synchronized (s.lock()) {
171+
s.println(bldr.toString());
100172
}
101-
return errors;
102173
}
103174

104-
@SuppressWarnings("unused")
105-
// useful when debugging but don't want to make part of publicly supported API
106-
private static String getStackTraceAsString(StackTraceElement[] stack) {
107-
StringBuilder s = new StringBuilder();
108-
boolean firstLine = true;
109-
for (StackTraceElement e : stack) {
110-
if (e.toString().startsWith("java.lang.Thread.getStackTrace")) {
111-
// we'll ignore this one
112-
continue;
113-
}
114-
if (!firstLine) {
115-
s.append("\n\t");
116-
}
117-
s.append(e.toString());
118-
firstLine = false;
175+
private void appendStackTrace(StringBuilder bldr, Throwable ex, String prefix) {
176+
bldr.append(prefix).append(ex).append("\n");
177+
for (StackTraceElement stackElement : ex.getStackTrace()) {
178+
bldr.append("\t\tat ").append(stackElement).append("\n");
179+
}
180+
if (ex.getCause() != null) {
181+
bldr.append("\tCaused by: ");
182+
appendStackTrace(bldr, ex.getCause(), "");
119183
}
120-
return s.toString();
121184
}
122185

123-
/* package-private */ static void attachCallingThreadStack(Throwable e, Throwable cause) {
124-
Set<Throwable> seenCauses = new HashSet<Throwable>();
186+
private abstract static class PrintStreamOrWriter {
187+
/** Returns the object to be locked when using this StreamOrWriter */
188+
abstract Object lock();
125189

126-
while (e.getCause() != null) {
127-
e = e.getCause();
128-
if (seenCauses.contains(e.getCause())) {
129-
break;
130-
} else {
131-
seenCauses.add(e.getCause());
132-
}
190+
/** Prints the specified string as a line on this StreamOrWriter */
191+
abstract void println(Object o);
192+
}
193+
194+
/**
195+
* Same abstraction and implementation as in JDK to allow PrintStream and PrintWriter to share implementation
196+
*/
197+
private static class WrappedPrintStream extends PrintStreamOrWriter {
198+
private final PrintStream printStream;
199+
200+
WrappedPrintStream(PrintStream printStream) {
201+
this.printStream = printStream;
202+
}
203+
204+
Object lock() {
205+
return printStream;
206+
}
207+
208+
void println(Object o) {
209+
printStream.println(o);
210+
}
211+
}
212+
213+
private static class WrappedPrintWriter extends PrintStreamOrWriter {
214+
private final PrintWriter printWriter;
215+
216+
WrappedPrintWriter(PrintWriter printWriter) {
217+
this.printWriter = printWriter;
133218
}
134-
// we now have 'e' as the last in the chain
135-
try {
136-
e.initCause(cause);
137-
} catch (Throwable t) {
138-
// ignore
139-
// the javadocs say that some Throwables (depending on how they're made) will never
140-
// let me call initCause without blowing up even if it returns null
219+
220+
Object lock() {
221+
return printWriter;
222+
}
223+
224+
void println(Object o) {
225+
printWriter.println(o);
141226
}
142227
}
143228

144-
/* package-private */ final static class CompositeExceptionCausalChain extends RuntimeException {
229+
/* package-private */final static class CompositeExceptionCausalChain extends RuntimeException {
145230
private static final long serialVersionUID = 3875212506787802066L;
146-
/* package-private */ static String MESSAGE = "Chain of Causes for CompositeException In Order Received =>";
231+
/* package-private */static String MESSAGE = "Chain of Causes for CompositeException In Order Received =>";
147232

148233
@Override
149234
public String getMessage() {
150235
return MESSAGE;
151236
}
152237
}
153238

154-
}
239+
private final List<Throwable> getListOfCauses(Throwable ex) {
240+
List<Throwable> list = new ArrayList<Throwable>();
241+
Throwable root = ex.getCause();
242+
if (root == null) {
243+
return list;
244+
} else {
245+
while(true) {
246+
list.add(root);
247+
if (root.getCause() == null) {
248+
return list;
249+
} else {
250+
root = root.getCause();
251+
}
252+
}
253+
}
254+
}
255+
}

rxjava/src/main/java/rx/internal/operators/OperatorDoOnEach.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import rx.Observable.Operator;
1919
import rx.Observer;
2020
import rx.Subscriber;
21+
import rx.exceptions.Exceptions;
2122
import rx.exceptions.OnErrorThrowable;
2223

2324
/**
@@ -54,6 +55,8 @@ public void onCompleted() {
5455

5556
@Override
5657
public void onError(Throwable e) {
58+
// need to throwIfFatal since we swallow errors after terminated
59+
Exceptions.throwIfFatal(e);
5760
if (done) {
5861
return;
5962
}

0 commit comments

Comments
 (0)