Ticket #13352: patch-bugreport-queue.patch
File patch-bugreport-queue.patch, 21.9 KB (added by , 9 years ago) |
---|
-
src/org/openstreetmap/josm/gui/MapView.java
diff --git a/src/org/openstreetmap/josm/gui/MapView.java b/src/org/openstreetmap/josm/gui/MapView.java index 63e4bb4..3a4b1ed 100644
a b import org.openstreetmap.josm.tools.AudioPlayer; 77 77 import org.openstreetmap.josm.tools.Shortcut; 78 78 import org.openstreetmap.josm.tools.Utils; 79 79 import org.openstreetmap.josm.tools.bugreport.BugReport; 80 import org.openstreetmap.josm.tools.bugreport.BugReportExceptionHandler;81 80 82 81 /** 83 82 * This is a component used in the {@link MapFrame} for browsing the map. It use is to … … LayerManager.LayerChangeListener, MainLayerManager.ActiveLayerChangeListener { 826 825 painter.paint(paintGraphics); 827 826 g.setPaintMode(); 828 827 } catch (RuntimeException t) { 829 //TODO: only display. 830 throw BugReport.intercept(t).put("layer", layer).put("bounds", box); 828 BugReport.intercept(t).put("layer", layer).put("bounds", box).warn(); 831 829 } 832 830 } 833 831 … … LayerManager.LayerChangeListener, MainLayerManager.ActiveLayerChangeListener { 836 834 */ 837 835 @Override 838 836 public void paint(Graphics g) { 839 if (!prepareToDraw()) { 837 try { 838 if (!prepareToDraw()) { 839 return; 840 } 841 } catch (RuntimeException e) { 842 BugReport.intercept(e).put("center", () -> getCenter()).warn(); 840 843 return; 841 844 } 842 845 … … LayerManager.LayerChangeListener, MainLayerManager.ActiveLayerChangeListener { 914 917 paintLayer(visibleLayers.get(i), tempG, box); 915 918 } 916 919 917 synchronized (temporaryLayers) { 918 for (MapViewPaintable mvp : temporaryLayers) { 919 try { 920 mvp.paint(tempG, this, box); 921 } catch (RuntimeException e) { 922 throw BugReport.intercept(e).put("mvp", mvp); 923 } 924 } 920 try { 921 drawTemporaryLayers(tempG, box); 922 } catch (RuntimeException e) { 923 BugReport.intercept(e).put("temporaryLayers", temporaryLayers).warn(); 925 924 } 926 925 927 926 // draw world borders 928 927 try { 929 928 drawWorldBorders(tempG); 930 929 } catch (RuntimeException e) { 931 throw BugReport.intercept(e).put("bounds", getProjection()::getWorldBoundsLatLon); 930 // getProjection() needs to be inside lambda to catch errors. 931 BugReport.intercept(e).put("bounds", () -> getProjection().getWorldBoundsLatLon()).warn(); 932 932 } 933 933 934 934 if (Main.isDisplayingMapView() && Main.map.filterDialog != null) { … … LayerManager.LayerChangeListener, MainLayerManager.ActiveLayerChangeListener { 970 970 super.paint(g); 971 971 } 972 972 973 private void drawTemporaryLayers(Graphics2D tempG, Bounds box) { 974 synchronized (temporaryLayers) { 975 for (MapViewPaintable mvp : temporaryLayers) { 976 try { 977 mvp.paint(tempG, this, box); 978 } catch (RuntimeException e) { 979 throw BugReport.intercept(e).put("mvp", mvp); 980 } 981 } 982 } 983 } 984 973 985 private void drawWorldBorders(Graphics2D tempG) { 974 986 tempG.setColor(Color.WHITE); 975 987 Bounds b = getProjection().getWorldBoundsLatLon(); … … LayerManager.LayerChangeListener, MainLayerManager.ActiveLayerChangeListener { 1025 1037 zoomTo(initialViewport); 1026 1038 initialViewport = null; 1027 1039 } 1028 if (BugReportExceptionHandler.exceptionHandlingInProgress())1029 return false;1030 1040 1031 1041 if (getCenter() == null) 1032 1042 return false; // no data loaded yet. -
src/org/openstreetmap/josm/tools/bugreport/BugReportDialog.java
diff --git a/src/org/openstreetmap/josm/tools/bugreport/BugReportDialog.java b/src/org/openstreetmap/josm/tools/bugreport/BugReportDialog.java index f4dec4c..e928e1b 100644
a b import javax.swing.JCheckBox; 18 18 import javax.swing.JComponent; 19 19 import javax.swing.JDialog; 20 20 import javax.swing.JLabel; 21 import javax.swing.JOptionPane; 21 22 import javax.swing.JPanel; 22 23 import javax.swing.KeyStroke; 23 24 import javax.swing.UIManager; 24 25 25 26 import org.openstreetmap.josm.Main; 26 27 import org.openstreetmap.josm.actions.ExpertToggleAction; 28 import org.openstreetmap.josm.gui.preferences.plugin.PluginPreference; 29 import org.openstreetmap.josm.gui.util.GuiHelper; 27 30 import org.openstreetmap.josm.gui.widgets.JMultilineLabel; 28 31 import org.openstreetmap.josm.gui.widgets.UrlLabel; 32 import org.openstreetmap.josm.plugins.PluginDownloadTask; 33 import org.openstreetmap.josm.plugins.PluginHandler; 29 34 import org.openstreetmap.josm.tools.GBC; 30 35 import org.openstreetmap.josm.tools.ImageProvider; 36 import org.openstreetmap.josm.tools.bugreport.BugReportQueue.SuppressionMode; 31 37 32 38 /** 33 39 * This is a dialog that can be used to display a bug report. … … public class BugReportDialog extends JDialog { 42 48 private final JPanel content = new JPanel(new GridBagLayout()); 43 49 private final BugReport report; 44 50 private final DebugTextDisplay textPanel; 45 private JCheckBox cbSuppress; 51 private JCheckBox cbSuppressSingle; 52 private JCheckBox cbSuppressAll; 46 53 47 54 /** 48 55 * Create a new dialog. … … public class BugReportDialog extends JDialog { 148 155 149 156 private void addIgnoreButton() { 150 157 JPanel panel = new JPanel(new GridBagLayout()); 151 cbSuppress = new JCheckBox(tr("Suppress further error dialogs for this session.")); 152 cbSuppress.setVisible(false); 153 panel.add(cbSuppress, GBC.std().fill(GBC.HORIZONTAL)); 158 cbSuppressSingle = new JCheckBox(tr("Suppress this error for this session.")); 159 cbSuppressSingle.setVisible(false); 160 panel.add(cbSuppressSingle, GBC.std(0, 0).fill(GBC.HORIZONTAL)); 161 cbSuppressAll = new JCheckBox(tr("Suppress further error dialogs for this session.")); 162 cbSuppressAll.setVisible(false); 163 panel.add(cbSuppressAll, GBC.std(0, 1).fill(GBC.HORIZONTAL)); 154 164 JButton ignore = new JButton(tr("Ignore this error.")); 155 165 ignore.addActionListener(e -> closeDialog()); 156 panel.add(ignore, GBC. eol());166 panel.add(ignore, GBC.std(1, 0).span(1, 2).anchor(GBC.CENTER)); 157 167 content.add(panel, GBC.eol().fill(GBC.HORIZONTAL).insets(20)); 158 168 } 159 169 … … public class BugReportDialog extends JDialog { 162 172 * @param showSuppress <code>true</code> to show the suppress errors checkbox. 163 173 */ 164 174 public void setShowSuppress(boolean showSuppress) { 165 cbSuppress.setVisible(showSuppress); 175 cbSuppressSingle.setVisible(showSuppress); 176 pack(); 177 } 178 179 /** 180 * Shows or hides the suppress all errors button 181 * @param showSuppress <code>true</code> to show the suppress errors checkbox. 182 */ 183 public void setShowSuppressAll(boolean showSuppress) { 184 cbSuppressAll.setVisible(showSuppress); 166 185 pack(); 167 186 } 168 187 … … public class BugReportDialog extends JDialog { 170 189 * Check if the checkbox to suppress further errors was selected 171 190 * @return <code>true</code> if the user wishes to suppress errors. 172 191 */ 173 public boolean shouldSuppressFurtherErrors() { 174 return cbSuppress.isSelected(); 192 public SuppressionMode shouldSuppressFurtherErrors() { 193 if (cbSuppressAll.isSelected()) { 194 return SuppressionMode.ALL; 195 } else if (cbSuppressSingle.isSelected()) { 196 return SuppressionMode.SAME; 197 } else { 198 return SuppressionMode.NONE; 199 } 175 200 } 176 201 177 202 private void closeDialog() { … … public class BugReportDialog extends JDialog { 201 226 } 202 227 return null; 203 228 } 229 230 /** 231 * Show the bug report for a given exception 232 * @param e The exception to display 233 * @param exceptionCounter A counter of how many exceptions have already been worked on 234 * @return The new suppression status 235 */ 236 public static SuppressionMode showFor(ReportedException e, int exceptionCounter) { 237 if (e.isOutOfMemory()) { 238 // do not translate the string, as translation may raise an exception 239 JOptionPane.showMessageDialog(Main.parent, "JOSM is out of memory. " + 240 "Strange things may happen.\nPlease restart JOSM with the -Xmx###M option,\n" + 241 "where ### is the number of MB assigned to JOSM (e.g. 256).\n" + 242 "Currently, " + Runtime.getRuntime().maxMemory()/1024/1024 + " MB are available to JOSM.", 243 "Error", 244 JOptionPane.ERROR_MESSAGE 245 ); 246 return SuppressionMode.NONE; 247 } else { 248 return GuiHelper.runInEDTAndWaitAndReturn(() -> { 249 PluginDownloadTask downloadTask = PluginHandler.updateOrdisablePluginAfterException(e); 250 if (downloadTask != null) { 251 // Ask for restart to install new plugin 252 PluginPreference.notifyDownloadResults( 253 Main.parent, downloadTask, !downloadTask.getDownloadedPlugins().isEmpty()); 254 return SuppressionMode.NONE; 255 } 256 257 BugReport report = new BugReport(e); 258 BugReportDialog dialog = new BugReportDialog(report); 259 dialog.setShowSuppress(exceptionCounter > 0); 260 dialog.setShowSuppressAll(exceptionCounter > 1); 261 dialog.setVisible(true); 262 return dialog.shouldSuppressFurtherErrors(); 263 }); 264 } 265 } 204 266 } -
src/org/openstreetmap/josm/tools/bugreport/BugReportExceptionHandler.java
diff --git a/src/org/openstreetmap/josm/tools/bugreport/BugReportExceptionHandler.java b/src/org/openstreetmap/josm/tools/bugreport/BugReportExceptionHandler.java index 461b73f..1decdd1 100644
a b 1 1 // License: GPL. For details, see LICENSE file. 2 2 package org.openstreetmap.josm.tools.bugreport; 3 3 4 import java.awt.GraphicsEnvironment;5 6 import javax.swing.JOptionPane;7 import javax.swing.SwingUtilities;8 9 import org.openstreetmap.josm.Main;10 import org.openstreetmap.josm.gui.preferences.plugin.PluginPreference;11 import org.openstreetmap.josm.plugins.PluginDownloadTask;12 import org.openstreetmap.josm.plugins.PluginHandler;13 14 4 /** 15 5 * An exception handler that asks the user to send a bug report. 16 6 * … … import org.openstreetmap.josm.plugins.PluginHandler; 19 9 */ 20 10 public final class BugReportExceptionHandler implements Thread.UncaughtExceptionHandler { 21 11 22 private static boolean handlingInProgress;23 private static volatile BugReporterThread bugReporterThread;24 private static int exceptionCounter;25 private static boolean suppressExceptionDialogs;26 27 static final class BugReporterThread extends Thread {28 29 private final class BugReporterWorker implements Runnable {30 private final PluginDownloadTask pluginDownloadTask;31 32 private BugReporterWorker(PluginDownloadTask pluginDownloadTask) {33 this.pluginDownloadTask = pluginDownloadTask;34 }35 36 @Override37 public void run() {38 // Then ask for submitting a bug report, for exceptions thrown from a plugin too, unless updated to a new version39 if (pluginDownloadTask == null) {40 askForBugReport(e);41 } else {42 // Ask for restart to install new plugin43 PluginPreference.notifyDownloadResults(44 Main.parent, pluginDownloadTask, !pluginDownloadTask.getDownloadedPlugins().isEmpty());45 }46 }47 }48 49 private final Throwable e;50 51 /**52 * Constructs a new {@code BugReporterThread}.53 * @param t the exception54 */55 private BugReporterThread(Throwable t) {56 super("Bug Reporter");57 this.e = t;58 }59 60 static void askForBugReport(final Throwable e) {61 if (GraphicsEnvironment.isHeadless()) {62 return;63 }64 BugReport report = new BugReport(BugReport.intercept(e));65 BugReportDialog dialog = new BugReportDialog(report);66 dialog.setShowSuppress(exceptionCounter > 1);67 dialog.setVisible(true);68 suppressExceptionDialogs = dialog.shouldSuppressFurtherErrors();69 }70 71 @Override72 public void run() {73 // Give the user a chance to deactivate the plugin which threw the exception (if it was thrown from a plugin)74 SwingUtilities.invokeLater(new BugReporterWorker(PluginHandler.updateOrdisablePluginAfterException(e)));75 }76 }77 78 12 @Override 79 13 public void uncaughtException(Thread t, Throwable e) { 80 14 handleException(e); … … public final class BugReportExceptionHandler implements Thread.UncaughtException 85 19 * @param e the exception 86 20 */ 87 21 public static synchronized void handleException(final Throwable e) { 88 if (handlingInProgress || suppressExceptionDialogs) 89 return; // we do not handle secondary exceptions, this gets too messy 90 if (bugReporterThread != null && bugReporterThread.isAlive()) 91 return; 92 handlingInProgress = true; 93 exceptionCounter++; 94 try { 95 Main.error(e); 96 if (Main.parent != null) { 97 if (e instanceof OutOfMemoryError) { 98 // do not translate the string, as translation may raise an exception 99 JOptionPane.showMessageDialog(Main.parent, "JOSM is out of memory. " + 100 "Strange things may happen.\nPlease restart JOSM with the -Xmx###M option,\n" + 101 "where ### is the number of MB assigned to JOSM (e.g. 256).\n" + 102 "Currently, " + Runtime.getRuntime().maxMemory()/1024/1024 + " MB are available to JOSM.", 103 "Error", 104 JOptionPane.ERROR_MESSAGE 105 ); 106 return; 107 } 108 109 bugReporterThread = new BugReporterThread(e); 110 bugReporterThread.start(); 111 } 112 } finally { 113 handlingInProgress = false; 114 } 22 BugReport.intercept(e).warn(); 115 23 } 116 24 117 25 /** … … public final class BugReportExceptionHandler implements Thread.UncaughtException 119 27 * @return {@code true} if an exception is currently being handled, {@code false} otherwise 120 28 */ 121 29 public static boolean exceptionHandlingInProgress() { 122 return handlingInProgress;30 return BugReportQueue.getInstance().exceptionHandlingInProgress(); 123 31 } 124 32 } -
new file src/org/openstreetmap/josm/tools/bugreport/BugReportQueue.java
diff --git a/src/org/openstreetmap/josm/tools/bugreport/BugReportQueue.java b/src/org/openstreetmap/josm/tools/bugreport/BugReportQueue.java new file mode 100644 index 0000000..69133c6
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.tools.bugreport; 3 4 import java.awt.GraphicsEnvironment; 5 import java.util.ArrayList; 6 import java.util.LinkedList; 7 import java.util.function.BiFunction; 8 9 import org.openstreetmap.josm.Main; 10 11 /** 12 * This class handles the display of the bug report dialog. 13 * @author Michael Zangl 14 * @since xxx 15 */ 16 public class BugReportQueue { 17 18 private static final BugReportQueue INSTANCE = new BugReportQueue(); 19 20 private LinkedList<ReportedException> reportsToDisplay = new LinkedList<>(); 21 private boolean suppressAllMessages; 22 private ArrayList<ReportedException> suppressFor = new ArrayList<>(); 23 private Thread displayThread; 24 private final BiFunction<ReportedException, Integer, SuppressionMode> bugReportHandler = getBestHandler(); 25 private int displayedErrors; 26 27 private boolean inReportDialog; 28 29 30 /** 31 * The suppression mode that should be used after the dialog was closed. 32 */ 33 public enum SuppressionMode { 34 /** 35 * Suppress no dialogs. 36 */ 37 NONE, 38 /** 39 * Suppress only the ones that are for the same error 40 */ 41 SAME, 42 /** 43 * Suppress all report dialogs 44 */ 45 ALL 46 } 47 48 /** 49 * Submit a new error to be displayed 50 * @param report The error to display 51 */ 52 public synchronized void submit(ReportedException report) { 53 if (suppressAllMessages || suppressFor.stream().anyMatch(report::isSame)) { 54 Main.info("User requested to skip error " + report); 55 } else if (reportsToDisplay.size() > 100 || reportsToDisplay.stream().filter(report::isSame).count() >= 10) { 56 Main.warn("Too many errors. Dropping " + report); 57 } else { 58 reportsToDisplay.add(report); 59 if (displayThread == null) { 60 displayThread = new Thread(this::displayAll, "bug-report-display"); 61 displayThread.start(); 62 } 63 notifyAll(); 64 } 65 } 66 67 private void displayAll() { 68 try { 69 while (true) { 70 ReportedException e = getNext(); 71 SuppressionMode suppress = displayFor(e); 72 handleDialogResult(e, suppress); 73 } 74 } catch (InterruptedException e) { 75 displayFor(BugReport.intercept(e)); 76 } 77 } 78 79 private synchronized void handleDialogResult(ReportedException e, SuppressionMode suppress) { 80 if (suppress == SuppressionMode.ALL) { 81 suppressAllMessages = true; 82 reportsToDisplay.clear(); 83 } else if (suppress == SuppressionMode.SAME) { 84 suppressFor.add(e); 85 reportsToDisplay.removeIf(e::isSame); 86 } 87 displayedErrors++; 88 inReportDialog = false; 89 } 90 91 private synchronized ReportedException getNext() throws InterruptedException { 92 while (reportsToDisplay.isEmpty()) { 93 wait(); 94 } 95 inReportDialog = true; 96 return reportsToDisplay.removeFirst(); 97 } 98 99 private SuppressionMode displayFor(ReportedException e) { 100 return bugReportHandler.apply(e, getDisplayedErrors()); 101 } 102 103 private synchronized int getDisplayedErrors() { 104 return displayedErrors; 105 } 106 107 /** 108 * Check if the dialog is shown. Should only be used for e.g. debugging. 109 * @return <code>true</code> if the exception handler is still showing the exception to the user. 110 */ 111 public synchronized boolean exceptionHandlingInProgress() { 112 return !reportsToDisplay.isEmpty() || inReportDialog; 113 } 114 115 private static BiFunction<ReportedException, Integer, SuppressionMode> getBestHandler() { 116 if (GraphicsEnvironment.isHeadless()) { 117 return (e, index) -> { e.printStackTrace(); return SuppressionMode.NONE; }; 118 } else { 119 return BugReportDialog::showFor; 120 } 121 } 122 123 public static BugReportQueue getInstance() { 124 return INSTANCE; 125 } 126 } -
src/org/openstreetmap/josm/tools/bugreport/ReportedException.java
diff --git a/src/org/openstreetmap/josm/tools/bugreport/ReportedException.java b/src/org/openstreetmap/josm/tools/bugreport/ReportedException.java index 22efa08..f7c4d36 100644
a b public class ReportedException extends RuntimeException { 65 65 */ 66 66 public void warn() { 67 67 methodWarningFrom = BugReport.getCallingMethod(2); 68 // TODO: Open the dialog. 68 try { 69 BugReportQueue.getInstance().submit(this); 70 } catch (RuntimeException e) { 71 try { 72 e.printStackTrace(); 73 } catch (RuntimeException e2) { 74 // we cannot do anything more... 75 // re-throwing this causes an infinite loop. 76 } 77 } 69 78 } 70 79 71 80 /** … … public class ReportedException extends RuntimeException { 242 251 243 252 @Override 244 253 public String toString() { 245 return new StringBuilder(48) 246 .append("CrashReportedException [on thread ") 247 .append(caughtOnThread) 248 .append(']') 249 .toString(); 254 return "ReportedException [thread=" + caughtOnThread + ", exception=" + exception 255 + ", methodWarningFrom=" + methodWarningFrom + "]"; 250 256 } 251 257 252 253 258 /** 254 259 * Check if this exception may be caused by a threading issue. 255 260 * @return <code>true</code> if it is. … … public class ReportedException extends RuntimeException { 261 266 } 262 267 263 268 /** 269 * Check if this is caused by an out of memory situaition 270 * @return <code>true</code> if it is. 271 */ 272 public boolean isOutOfMemory() { 273 return StreamUtils.toStream(CauseTraceIterator::new).anyMatch(t -> t instanceof OutOfMemoryError); 274 } 275 276 /** 264 277 * Iterates over the causes for this exception. Ignores cycles and aborts iteration then. 265 278 * @author Michal Zangl 266 279 * @since 10585 -
test/unit/org/openstreetmap/josm/tools/bugreport/BugReportExceptionHandlerTest.java
diff --git a/test/unit/org/openstreetmap/josm/tools/bugreport/BugReportExceptionHandlerTest.java b/test/unit/org/openstreetmap/josm/tools/bugreport/BugReportExceptionHandlerTest.java index f479d0b..a030600 100644
a b public class BugReportExceptionHandlerTest { 21 21 } 22 22 23 23 /** 24 * Unit test for {@link BugReportExceptionHandler.BugReporterThread#askForBugReport} method.25 */26 @Test27 public void testAskForBugReport() {28 BugReportExceptionHandler.BugReporterThread.askForBugReport(new Exception("testAskForBugReport"));29 }30 31 /**32 24 * Unit test for {@link BugReportExceptionHandler#handleException} method. 33 25 */ 34 26 @Test