/*******************************************************************************
* Breakout Cave Survey Visualizer
*
* Copyright (C) 2014 James Edwards
*
* jedwards8 at fastmail dot fm
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option) any later
* version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*******************************************************************************/
package org.andork.uncaught;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
/**
* An {@link UncaughtExceptionHandler} that groups uncaught exceptions with the
* same class, localized message, and stack trace, and doesn't report more than
* twice for a given group in a given time interval. When it receives an
* uncaught exception, if it hasn't seen a matching exception since the last
* report, it passes the exception onto the downstream
* {@code UncaughtExceptionHandler}; otherwise, it increments the count for that
* exception and schedules a timer to report (via Exceptions sent to the
* downstream {@code UncaughtExceptionHandler} how many of each type of
* exception occured since the last report.
*
* @author andy.edwards
*/
public class GroupingUncaughtExceptionHandler implements UncaughtExceptionHandler {
private static class Trace {
int hashCode;
Throwable throwable;
StackTraceElement[] trace;
public Trace(Throwable throwable) {
this.throwable = throwable;
trace = throwable.getStackTrace();
hashCode = throwable.getClass().hashCode() ^ 29 * Arrays.hashCode(trace);
}
@Override
public boolean equals(Object o) {
if (o instanceof Trace) {
Trace t = (Trace) o;
return throwable.getClass().equals(t.throwable.getClass()) &&
throwable.getLocalizedMessage().equals(t.throwable.getLocalizedMessage()) &&
Arrays.equals(trace, t.trace);
}
return false;
}
@Override
public int hashCode() {
return hashCode;
}
}
private final Object lock = new Object();
private long lastDump;
private long nextDump;
private final Map<Trace, Integer> delayedMap = new LinkedHashMap<Trace, Integer>();
private final UncaughtExceptionHandler downstream;
private final java.util.Timer timer;
private final long dumpInterval;
public GroupingUncaughtExceptionHandler(UncaughtExceptionHandler downstream, long dumpInterval) {
super();
this.downstream = downstream;
this.dumpInterval = dumpInterval;
timer = new Timer(true);
}
protected void dumpGroups() {
synchronized (lock) {
for (Map.Entry<Trace, Integer> entry : delayedMap.entrySet()) {
Trace groupTrace = entry.getKey();
int groupCount = entry.getValue();
if (groupCount > 0) {
downstream.uncaughtException(Thread.currentThread(),
new Exception("There were " + groupCount +
" uncaught exceptions of the following form since last notice",
groupTrace.throwable));
}
}
delayedMap.clear();
lastDump = System.currentTimeMillis();
}
}
@Override
public void uncaughtException(Thread t, Throwable e) {
synchronized (lock) {
Trace trace = null;
try {
trace = new Trace(e);
} catch (Exception ex) {
downstream.uncaughtException(Thread.currentThread(), new Exception("Failed to hash Throwable", e));
return;
}
Integer prevCount = delayedMap.get(trace);
if (prevCount == null) {
downstream.uncaughtException(t, e);
delayedMap.put(trace, 0);
} else {
delayedMap.put(trace, prevCount + 1);
long currentTime = System.currentTimeMillis();
if (prevCount == 1) {
if (nextDump <= lastDump) {
nextDump = Math.max(lastDump + dumpInterval, currentTime + 1);
timer.schedule(new TimerTask() {
@Override
public void run() {
dumpGroups();
}
}, Math.max(nextDump - System.currentTimeMillis(), 1));
}
}
}
}
}
}