1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 package org.spf4j.base;
33
34 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
35 import gnu.trove.set.hash.THashSet;
36 import java.io.IOException;
37 import java.io.PrintStream;
38 import java.io.UncheckedIOException;
39 import java.sql.SQLRecoverableException;
40 import java.sql.SQLTransientException;
41 import java.util.ArrayDeque;
42 import java.util.ArrayList;
43 import java.util.Iterator;
44 import java.util.List;
45 import java.util.Set;
46 import java.util.concurrent.ExecutionException;
47 import java.util.concurrent.TimeoutException;
48 import java.util.function.Function;
49 import java.util.function.Predicate;
50 import java.util.logging.Level;
51 import java.util.logging.Logger;
52 import javax.annotation.CheckReturnValue;
53 import javax.annotation.Nonnull;
54 import javax.annotation.Nullable;
55 import javax.annotation.ParametersAreNonnullByDefault;
56 import org.spf4j.base.avro.AThrowables;
57 import org.spf4j.base.avro.RemoteException;
58 import org.spf4j.ds.IdentityHashSet;
59
60
61
62
63
64
65 @ParametersAreNonnullByDefault
66 @SuppressFBWarnings("FCCD_FIND_CLASS_CIRCULAR_DEPENDENCY")
67
68 public final class Throwables {
69
70
71
72
73 public static final String SUPPRESSED_CAPTION = "Suppressed: ";
74
75
76
77 public static final String CAUSE_CAPTION = "Caused by: ";
78
79
80 private static final int MAX_SUPPRESS_CHAIN
81 = Integer.getInteger("spf4j.throwables.defaultMaxSuppressChain", 100);
82
83 private static final PackageDetail DEFAULT_PACKAGE_DETAIL
84 = PackageDetail.valueOf(System.getProperty("spf4j.throwables.defaultStackTracePackageDetail", "SHORT"));
85
86
87 private static final boolean DEFAULT_TRACE_ELEMENT_ABBREVIATION
88 = Boolean.parseBoolean(System.getProperty("spf4j.throwables.defaultStackTraceAbbreviation", "true"));
89
90
91 private static volatile Predicate<Throwable> nonRecoverableClassificationPredicate = new Predicate<Throwable>() {
92 @Override
93 @SuppressFBWarnings("ITC_INHERITANCE_TYPE_CHECKING")
94 public boolean test(final Throwable t) {
95
96 if (t instanceof Error && !(t instanceof StackOverflowError)
97 && !(t instanceof AssertionError)
98 && !(t.getClass().getName().endsWith("TokenMgrError"))) {
99 return true;
100 }
101 if (t instanceof IOException) {
102 String message = t.getMessage();
103 if (message != null && message.contains("Too many open files")) {
104 return true;
105 }
106 }
107 return false;
108 }
109 };
110
111
112 private static volatile Function<Throwable, Boolean> isRetryablePredicate =
113 new Function<Throwable, Boolean>() {
114
115
116
117
118
119 @Override
120 @SuppressFBWarnings({"ITC_INHERITANCE_TYPE_CHECKING", "NP_BOOLEAN_RETURN_NULL" })
121 @Nullable
122 public Boolean apply(final Throwable t) {
123
124 if (Throwables.containsNonRecoverable(t)) {
125 return Boolean.FALSE;
126 }
127
128 Throwable e = Throwables.firstCause(t,
129 ex -> {
130 String exClassName = ex.getClass().getName();
131 return (ex instanceof SQLTransientException
132 || ex instanceof SQLRecoverableException
133 || (ex instanceof IOException && !exClassName.contains("Json"))
134 || ex instanceof TimeoutException
135 || ex instanceof UncheckedTimeoutException
136 || (exClassName.contains("Transient")
137 && !exClassName.contains("NonTransient")));
138 });
139 return e != null ? Boolean.TRUE : null;
140 }
141 };
142
143
144 private Throwables() {
145 }
146
147
148
149
150
151
152
153
154 @CheckReturnValue
155 public static boolean isRetryable(final Throwable value) {
156 Boolean result = isRetryablePredicate.apply(value);
157 return result == null ? false : result;
158 }
159
160 @Nonnull
161 @CheckReturnValue
162 public static Function<Throwable, Boolean> getIsRetryablePredicate() {
163 return isRetryablePredicate;
164 }
165
166 public static void setIsRetryablePredicate(final Function<Throwable, Boolean> isRetryablePredicate) {
167 Throwables.isRetryablePredicate = isRetryablePredicate;
168 }
169
170
171 public static int getNrSuppressedExceptions(final Throwable t) {
172 return UnsafeThrowable.getSuppressedNoCopy(t).size();
173 }
174
175 public static int getNrRecursiveSuppressedExceptions(final Throwable t) {
176 final List<Throwable> suppressedExceptions = UnsafeThrowable.getSuppressedNoCopy(t);
177 int count = 0;
178 for (Throwable se : suppressedExceptions) {
179 count += 1 + getNrRecursiveSuppressedExceptions(se);
180 }
181 return count;
182 }
183
184 @Nullable
185 public static Throwable removeOldestSuppressedRecursive(final Throwable t) {
186 final List<Throwable> suppressedExceptions = UnsafeThrowable.getSuppressedNoCopy(t);
187 if (suppressedExceptions.isEmpty()) {
188 return null;
189 } else {
190 Throwable ex = suppressedExceptions.get(0);
191 if (getNrSuppressedExceptions(ex) > 0) {
192 return removeOldestSuppressedRecursive(ex);
193 } else {
194 return suppressedExceptions.remove(0);
195 }
196 }
197 }
198
199 @Nullable
200 public static Throwable removeOldestSuppressed(final Throwable t) {
201 final List<Throwable> suppressedExceptions = UnsafeThrowable.getSuppressedNoCopy(t);
202 if (suppressedExceptions.isEmpty()) {
203 return null;
204 } else {
205 return suppressedExceptions.remove(0);
206 }
207 }
208
209
210 public static final class TrimmedException extends Exception {
211
212 public TrimmedException(final String message) {
213 super(message);
214 }
215
216 @Override
217 public synchronized Throwable fillInStackTrace() {
218 return this;
219 }
220 }
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237 @CheckReturnValue
238 @Deprecated
239 public static <T extends Throwable> T suppress(@Nonnull final T t, @Nonnull final Throwable suppressed) {
240 return suppress(t, suppressed, MAX_SUPPRESS_CHAIN);
241 }
242
243
244
245
246
247
248
249
250
251
252 public static void suppressLimited(@Nonnull final Throwable t, @Nonnull final Throwable suppressed) {
253 suppressLimited(t, suppressed, MAX_SUPPRESS_CHAIN);
254 }
255
256 @SuppressFBWarnings("NOS_NON_OWNED_SYNCHRONIZATION")
257 public static void suppressLimited(@Nonnull final Throwable t, @Nonnull final Throwable suppressed,
258 final int maxSuppressed) {
259 if (contains(t, suppressed)) {
260 Logger.getLogger(Throwables.class.getName()).log(Level.INFO,
261 "Circular suppression attempted", new RuntimeException(suppressed));
262 return;
263 }
264 synchronized (t) {
265 t.addSuppressed(suppressed);
266 while (getNrRecursiveSuppressedExceptions(t) > maxSuppressed) {
267 if (removeOldestSuppressedRecursive(t) == null) {
268 throw new IllegalArgumentException("Impossible state for " + t);
269 }
270 }
271 }
272 }
273
274
275 @CheckReturnValue
276 public static <T extends Throwable> T suppress(@Nonnull final T t, @Nonnull final Throwable suppressed,
277 final int maxSuppressed) {
278 T clone;
279 try {
280 clone = Objects.clone(t);
281 } catch (RuntimeException ex) {
282 t.addSuppressed(ex);
283 clone = t;
284 }
285 if (contains(t, suppressed)) {
286 return clone;
287 }
288 clone.addSuppressed(suppressed);
289 while (getNrRecursiveSuppressedExceptions(clone) > maxSuppressed) {
290 if (removeOldestSuppressedRecursive(clone) == null) {
291 throw new IllegalArgumentException("Impossible state for " + clone);
292 }
293 }
294 return clone;
295 }
296
297
298
299
300
301
302
303
304
305
306
307
308
309 public static Throwable[] getSuppressed(final Throwable t) {
310 if (t instanceof Iterable) {
311
312 Throwable[] osuppressed = t.getSuppressed();
313 List<Throwable> suppressed = new ArrayList<>(osuppressed.length);
314 Set<Throwable> ignore = new IdentityHashSet<>();
315 ignore.addAll(java.util.Arrays.asList(osuppressed));
316 suppressed.addAll(ignore);
317 ignore.addAll(com.google.common.base.Throwables.getCausalChain(t));
318 Iterator it = ((Iterable) t).iterator();
319 while (it.hasNext()) {
320 Object next = it.next();
321 if (next instanceof Throwable) {
322 if (ignore.contains(next)) {
323 continue;
324 }
325 suppressed.add((Throwable) next);
326 ignore.addAll(com.google.common.base.Throwables.getCausalChain((Throwable) next));
327 } else {
328 break;
329 }
330 }
331 return suppressed.toArray(new Throwable[suppressed.size()]);
332 } else {
333 return t.getSuppressed();
334 }
335
336 }
337
338 public static void writeTo(final StackTraceElement element, @Nullable final StackTraceElement previous,
339 final Appendable to, final PackageDetail detail,
340 final boolean abbreviatedTraceElement)
341 throws IOException {
342 String currClassName = element.getClassName();
343 String prevClassName = previous == null ? null : previous.getClassName();
344 if (abbreviatedTraceElement) {
345 if (currClassName.equals(prevClassName)) {
346 to.append('^');
347 } else {
348 writeAbreviatedClassName(currClassName, to);
349 }
350 } else {
351 to.append(currClassName);
352 }
353 to.append('.');
354 to.append(element.getMethodName());
355 String currFileName = element.getFileName();
356 String fileName = currFileName;
357 if (abbreviatedTraceElement && java.util.Objects.equals(currFileName,
358 previous == null ? null : previous.getFileName())) {
359 fileName = "^";
360 }
361 final int lineNumber = element.getLineNumber();
362 if (element.isNativeMethod()) {
363 to.append("(Native Method)");
364 } else if (fileName == null) {
365 to.append("(Unknown Source)");
366 } else if (lineNumber >= 0) {
367 to.append('(').append(fileName).append(':')
368 .append(Integer.toString(lineNumber)).append(')');
369 } else {
370 to.append('(').append(fileName).append(')');
371 }
372 if (detail == PackageDetail.NONE) {
373 return;
374 }
375 if (abbreviatedTraceElement && currClassName.equals(prevClassName)) {
376 to.append("[^]");
377 return;
378 }
379 org.spf4j.base.avro.PackageInfo pInfo = PackageInfo.getPackageInfo(currClassName);
380 if (abbreviatedTraceElement && prevClassName != null && pInfo.equals(PackageInfo.getPackageInfo(prevClassName))) {
381 to.append("[^]");
382 return;
383 }
384 if (!pInfo.getUrl().isEmpty() || !pInfo.getVersion().isEmpty()) {
385 String jarSourceUrl = pInfo.getUrl();
386 String version = pInfo.getVersion();
387 to.append('[');
388 if (!jarSourceUrl.isEmpty()) {
389 if (detail == PackageDetail.SHORT) {
390 String url = jarSourceUrl;
391 int lastIndexOf = url.lastIndexOf('/');
392 if (lastIndexOf >= 0) {
393 int lpos = url.length() - 1;
394 if (lastIndexOf == lpos) {
395 int prevSlPos = url.lastIndexOf('/', lpos - 1);
396 if (prevSlPos < 0) {
397 to.append(url);
398 } else {
399 to.append(url, prevSlPos + 1, url.length());
400 }
401 } else {
402 to.append(url, lastIndexOf + 1, url.length());
403 }
404 } else {
405 to.append(url);
406 }
407 } else {
408 to.append(jarSourceUrl);
409 }
410 } else {
411 to.append("na");
412 }
413 if (!version.isEmpty()) {
414 to.append(':');
415 to.append(version);
416 }
417 to.append(']');
418 }
419 }
420
421
422
423
424 public enum PackageDetail {
425
426
427
428 NONE,
429
430
431
432 SHORT,
433
434
435
436 LONG
437
438 }
439
440 public static String toString(final Throwable t) {
441 return toString(t, DEFAULT_PACKAGE_DETAIL);
442 }
443
444 public static String toString(final Throwable t, final PackageDetail detail) {
445 return toString(t, detail, DEFAULT_TRACE_ELEMENT_ABBREVIATION);
446 }
447
448 public static String toString(final Throwable t, final PackageDetail detail, final boolean abbreviatedTraceElement) {
449 StringBuilder sb = toStringBuilder(t, detail, abbreviatedTraceElement);
450 return sb.toString();
451 }
452
453 public static StringBuilder toStringBuilder(final Throwable t, final PackageDetail detail) {
454 return toStringBuilder(t, detail, DEFAULT_TRACE_ELEMENT_ABBREVIATION);
455 }
456
457 public static StringBuilder toStringBuilder(final Throwable t, final PackageDetail detail,
458 final boolean abbreviatedTraceElement) {
459 StringBuilder sb = new StringBuilder(1024);
460 try {
461 writeTo(t, sb, detail, abbreviatedTraceElement);
462 } catch (IOException ex) {
463 throw new RuntimeException(ex);
464 }
465 return sb;
466 }
467
468 public static void writeTo(@Nonnull final Throwable t, @Nonnull final PrintStream to,
469 @Nonnull final PackageDetail detail) {
470 writeTo(t, to, detail, DEFAULT_TRACE_ELEMENT_ABBREVIATION);
471 }
472
473 @SuppressFBWarnings({"OCP_OVERLY_CONCRETE_PARAMETER"})
474 public static void writeTo(@Nonnull final Throwable t, @Nonnull final PrintStream to,
475 @Nonnull final PackageDetail detail, final boolean abbreviatedTraceElement) {
476 StringBuilder sb = new StringBuilder(1024);
477 try {
478 writeTo(t, sb, detail, abbreviatedTraceElement);
479 to.append(sb);
480 } catch (IOException ex) {
481 throw new UncheckedIOException(ex);
482 }
483 }
484
485 public static void writeTo(final Throwable t, final Appendable to, final PackageDetail detail) throws IOException {
486 writeTo(t, to, detail, DEFAULT_TRACE_ELEMENT_ABBREVIATION);
487 }
488
489 public static void writeTo(final Throwable t, final Appendable to, final PackageDetail detail,
490 final String prefix) throws IOException {
491 writeTo(t, to, detail, DEFAULT_TRACE_ELEMENT_ABBREVIATION, prefix);
492 }
493
494 public static void writeTo(final Throwable t, final Appendable to, final PackageDetail detail,
495 final boolean abbreviatedTraceElement) throws IOException {
496 writeTo(t, to, detail, abbreviatedTraceElement, "");
497 }
498
499 public static void writeTo(final Throwable t, final Appendable to, final PackageDetail detail,
500 final boolean abbreviatedTraceElement, final String prefix) throws IOException {
501 if (t instanceof RemoteException) {
502 AThrowables.writeTo((RemoteException) t, to, detail, abbreviatedTraceElement, prefix);
503 return;
504 }
505 to.append(prefix);
506 writeMessageString(to, t);
507 to.append('\n');
508 writeThrowableDetail(t, to, detail, abbreviatedTraceElement, prefix);
509 }
510
511 public static void writeThrowableDetail(final Throwable t, final Appendable to, final PackageDetail detail,
512 final boolean abbreviatedTraceElement, final String prefix) throws IOException {
513 StackTraceElement[] trace = t.getStackTrace();
514 writeTo(trace, to, detail, abbreviatedTraceElement, prefix);
515 Throwable[] suppressed = getSuppressed(t);
516 Throwable ourCause = t.getCause();
517 if (ourCause == null && suppressed.length == 0) {
518 return;
519 }
520 Set<Throwable> dejaVu = new IdentityHashSet<Throwable>();
521 dejaVu.add(t);
522
523 for (Throwable se : suppressed) {
524 printEnclosedStackTrace(se, to, trace, SUPPRESSED_CAPTION, prefix + "\t",
525 dejaVu, detail, abbreviatedTraceElement);
526 }
527
528 if (ourCause != null) {
529 printEnclosedStackTrace(ourCause, to, trace, CAUSE_CAPTION, prefix, dejaVu, detail, abbreviatedTraceElement);
530 }
531 }
532
533 public static void writeMessageString(final Appendable to, final Throwable t) throws IOException {
534 to.append(t.getClass().getName());
535 String message = t.getMessage();
536 if (message != null) {
537 to.append(':').append(message);
538 }
539 }
540
541 public static void writeTo(final StackTraceElement[] trace, final Appendable to, final PackageDetail detail,
542 final boolean abbreviatedTraceElement)
543 throws IOException {
544 writeTo(trace, to, detail, abbreviatedTraceElement, "");
545 }
546
547 public static void writeTo(final StackTraceElement[] trace, final Appendable to, final PackageDetail detail,
548 final boolean abbreviatedTraceElement, final String prefix)
549 throws IOException {
550 StackTraceElement prevElem = null;
551 for (StackTraceElement traceElement : trace) {
552 to.append(prefix);
553 to.append("\tat ");
554 writeTo(traceElement, prevElem, to, detail, abbreviatedTraceElement);
555 to.append('\n');
556 prevElem = traceElement;
557 }
558 }
559
560 public static int commonFrames(final StackTraceElement[] trace, final StackTraceElement[] enclosingTrace) {
561 int from = trace.length - 1;
562 int m = from;
563 int n = enclosingTrace.length - 1;
564 while (m >= 0 && n >= 0 && trace[m].equals(enclosingTrace[n])) {
565 m--;
566 n--;
567 }
568 return from - m;
569 }
570
571 private static void printEnclosedStackTrace(final Throwable t, final Appendable s,
572 final StackTraceElement[] enclosingTrace,
573 final String caption,
574 final String prefix,
575 final Set<Throwable> dejaVu,
576 final PackageDetail detail,
577 final boolean abbreviatedTraceElement) throws IOException {
578 if (dejaVu.contains(t)) {
579 s.append("\t[CIRCULAR REFERENCE:");
580 writeMessageString(s, t);
581 s.append("]\n");
582 } else {
583 dejaVu.add(t);
584
585 StackTraceElement[] trace = t.getStackTrace();
586 int framesInCommon = commonFrames(trace, enclosingTrace);
587 int m = trace.length - framesInCommon;
588
589 s.append(prefix).append(caption);
590 writeMessageString(s, t);
591 s.append('\n');
592 StackTraceElement prev = null;
593 for (int i = 0; i < m; i++) {
594 s.append(prefix).append("\tat ");
595 StackTraceElement ste = trace[i];
596 writeTo(ste, prev, s, detail, abbreviatedTraceElement);
597 s.append('\n');
598 prev = ste;
599 }
600 if (framesInCommon != 0) {
601 s.append(prefix).append("\t... ").append(Integer.toString(framesInCommon)).append(" more");
602 s.append('\n');
603 }
604
605
606 for (Throwable se : getSuppressed(t)) {
607 printEnclosedStackTrace(se, s, trace, SUPPRESSED_CAPTION, prefix + '\t',
608 dejaVu, detail, abbreviatedTraceElement);
609 }
610
611
612 Throwable ourCause = t.getCause();
613 if (ourCause != null) {
614 printEnclosedStackTrace(ourCause, s, trace, CAUSE_CAPTION, prefix,
615 dejaVu, detail, abbreviatedTraceElement);
616 }
617 }
618 }
619
620
621
622
623
624
625 public static boolean isNonRecoverable(@Nonnull final Throwable t) {
626 return nonRecoverableClassificationPredicate.test(t);
627 }
628
629
630
631
632
633
634 public static boolean containsNonRecoverable(@Nonnull final Throwable t) {
635 return contains(t, nonRecoverableClassificationPredicate);
636 }
637
638
639
640
641
642
643
644
645 public static boolean contains(@Nonnull final Throwable t, final Predicate<Throwable> predicate) {
646 return first(t, predicate) != null;
647 }
648
649
650
651
652
653
654
655
656
657 public static boolean contains(@Nonnull final Throwable t, @Nonnull final Throwable toLookFor) {
658 return first(t, (x) -> x == toLookFor) != null;
659 }
660
661
662
663
664
665
666
667
668 @Nullable
669 @CheckReturnValue
670 public static <T extends Throwable> T first(@Nonnull final Throwable t, final Class<T> clasz) {
671 return (T) first(t, (Throwable th) -> clasz.isAssignableFrom(th.getClass()));
672 }
673
674
675
676
677
678
679
680
681 @Nullable
682 @CheckReturnValue
683 public static Throwable first(@Nonnull final Throwable t, final Predicate<Throwable> predicate) {
684 if (predicate.test(t)) {
685 return t;
686 }
687 ArrayDeque<Throwable> toScan = new ArrayDeque<>();
688 Throwable cause = t.getCause();
689 if (cause != null) {
690 toScan.addFirst(cause);
691 }
692 for (Throwable supp : getSuppressed(t)) {
693 toScan.addLast(supp);
694 }
695 Throwable th;
696 THashSet<Throwable> seen = new IdentityHashSet<>();
697 while ((th = toScan.pollFirst()) != null) {
698 if (seen.contains(th)) {
699 continue;
700 }
701 if (predicate.test(th)) {
702 return th;
703 } else {
704 cause = th.getCause();
705 if (cause != null) {
706 toScan.addFirst(cause);
707 }
708 for (Throwable supp : getSuppressed(th)) {
709 toScan.addLast(supp);
710 }
711 }
712 seen.add(th);
713 }
714 return null;
715 }
716
717
718
719
720
721
722
723
724
725 @Nullable
726 @CheckReturnValue
727 public static Throwable firstCause(@Nonnull final Throwable throwable, final Predicate<Throwable> predicate) {
728 Throwable t = throwable;
729 do {
730 if (predicate.test(t)) {
731 return t;
732 }
733 t = t.getCause();
734 } while (t != null);
735 return null;
736 }
737
738
739 public static Predicate<Throwable> getNonRecoverablePredicate() {
740 return nonRecoverableClassificationPredicate;
741 }
742
743
744
745
746
747 public static void setNonRecoverablePredicate(final Predicate<Throwable> predicate) {
748 Throwables.nonRecoverableClassificationPredicate = predicate;
749 }
750
751 public static void writeAbreviatedClassName(final String className, final Appendable writeTo) throws IOException {
752 int ldIdx = className.lastIndexOf('.');
753 if (ldIdx < 0) {
754 writeTo.append(className);
755 return;
756 }
757 boolean isPreviousDot = true;
758 for (int i = 0; i < ldIdx; i++) {
759 char c = className.charAt(i);
760 boolean isCurrentCharDot = c == '.';
761 if (isPreviousDot || isCurrentCharDot) {
762 writeTo.append(c);
763 }
764 isPreviousDot = isCurrentCharDot;
765 }
766 writeTo.append(className, ldIdx, className.length());
767 }
768
769 @SuppressFBWarnings("ITC_INHERITANCE_TYPE_CHECKING")
770 public static void throwException(final Exception ex) throws IOException, InterruptedException,
771 ExecutionException, TimeoutException {
772 if (ex instanceof IOException) {
773 throw (IOException) ex;
774 } else if (ex instanceof InterruptedException) {
775 throw (InterruptedException) ex;
776 } else if (ex instanceof ExecutionException) {
777 throw (ExecutionException) ex;
778 } else if (ex instanceof TimeoutException) {
779 throw (TimeoutException) ex;
780 } else {
781 throw new ExecutionException(ex);
782 }
783 }
784
785
786 }