From 90e4df323ea892efb72df6e496da5b790877ea43 Mon Sep 17 00:00:00 2001 From: Stian Grenborgen Date: Wed, 14 Sep 2022 20:14:04 +0200 Subject: [PATCH] Handles uncaught errors (OutOfMemoryError, StackOverflowError etc) by freeing memory, showing an error message and force quit. --- data/strings/FreeColMessages.properties | 1 + src/net/sf/freecol/client/FreeColClient.java | 33 ++++++++++++++++++++ src/net/sf/freecol/client/gui/GUI.java | 10 ++++++ src/net/sf/freecol/client/gui/SwingGUI.java | 13 ++++++++ 4 files changed, 57 insertions(+) diff --git a/data/strings/FreeColMessages.properties b/data/strings/FreeColMessages.properties index 2d339da00..12f4d577e 100644 --- a/data/strings/FreeColMessages.properties +++ b/data/strings/FreeColMessages.properties @@ -2789,6 +2789,7 @@ model.unit.hardyPioneer.noMoreTools=%location%: Your Hardy Pioneer has used up a # -10-- Client # main: main() and support in FreeCol.java +error.seriousError=An error occurred that is forcing FreeCol to exit. Please refer to the log file for more information. error.couldNotFind=An error occurred because file %name% could not be found error.couldNotLoad=An error occurred while trying to load the game from file %name% error.couldNotLoadDifficulty=An error occurred while trying to load difficulty options from file %name%. diff --git a/src/net/sf/freecol/client/FreeColClient.java b/src/net/sf/freecol/client/FreeColClient.java index 29eed91da..b919d8891 100644 --- a/src/net/sf/freecol/client/FreeColClient.java +++ b/src/net/sf/freecol/client/FreeColClient.java @@ -71,6 +71,9 @@ public final class FreeColClient { private static final Logger logger = Logger.getLogger(FreeColClient.class.getName()); + @SuppressWarnings("unused") + private byte[] memoryToBeFreedOnOutOfMemory = new byte[10 * 1024 * 1024]; + private final ConnectController connectController; private final PreGameController preGameController; @@ -167,6 +170,7 @@ public final class FreeColClient { if (FreeCol.getHeadless() && savedGame == null && spec == null) { FreeCol.fatal(logger, Messages.message("client.headlessRequires")); } + mapEditor = false; // Look for base data directory and get base resources loaded. @@ -246,6 +250,8 @@ public final class FreeColClient { // Initialize Sound (depends on client options) this.soundController = new SoundController(this, sound); + overrideDefaultUncaughtExceptionHandler(); + /* * Please do NOT move preloading before mods are loaded -- as that * might cause some images to be loaded from base and other images @@ -1010,4 +1016,31 @@ public final class FreeColClient { } FreeCol.quit(0); } + + private void overrideDefaultUncaughtExceptionHandler() { + // This overrides the handler in FreeCol: + Thread.setDefaultUncaughtExceptionHandler((Thread thread, Throwable e) -> { + // Free enough space to ensure we can perform the next operations. + if (e instanceof Error) { + memoryToBeFreedOnOutOfMemory = null; + gui.emergencyPurge(); + } + + final boolean seriousError = (e instanceof Error); + try { + logger.log(Level.WARNING, "Uncaught exception from thread: " + thread, e); + + if (seriousError) { + gui.showErrorPanel(Messages.message("error.seriousError"), () -> { + System.exit(1); + }); + } + } catch (Throwable t) { + if (seriousError) { + t.printStackTrace(); + System.exit(1); + } + } + }); + } } diff --git a/src/net/sf/freecol/client/gui/GUI.java b/src/net/sf/freecol/client/gui/GUI.java index 7a5af5c56..6f6009abe 100644 --- a/src/net/sf/freecol/client/gui/GUI.java +++ b/src/net/sf/freecol/client/gui/GUI.java @@ -2506,4 +2506,14 @@ public class GUI extends FreeColClientHolder { public boolean canGameChangingModsBeAdded() { return true; } + + + /** + * A method to be called only on serious Errors in order + * to ensure sufficient memory for displaying an error + * message. + */ + public void emergencyPurge() { + + } } diff --git a/src/net/sf/freecol/client/gui/SwingGUI.java b/src/net/sf/freecol/client/gui/SwingGUI.java index 0202e2edd..dbc0c5230 100644 --- a/src/net/sf/freecol/client/gui/SwingGUI.java +++ b/src/net/sf/freecol/client/gui/SwingGUI.java @@ -56,6 +56,7 @@ import net.sf.freecol.FreeCol; import net.sf.freecol.client.ClientOptions; import net.sf.freecol.client.FreeColClient; import net.sf.freecol.client.control.MapTransform; +import net.sf.freecol.client.control.SoundController; import net.sf.freecol.client.gui.animation.Animation; import net.sf.freecol.client.gui.animation.Animations; // Special dialogs and panels @@ -1373,6 +1374,18 @@ public class SwingGUI extends GUI { refresh(); } + /** + * {@inheritDoc} + */ + @Override + public void emergencyPurge() { + imageCache.clear(); + + final SoundController sc = getFreeColClient().getSoundController(); + sc.setDefaultPlaylist(List.of()); + sc.playMusic(null); + } + // Highest level panel and dialog handling