diff --git a/src/main/java/org/qora/ApplyUpdate.java b/src/main/java/org/qora/ApplyUpdate.java index b56c6e0d..af163eca 100644 --- a/src/main/java/org/qora/ApplyUpdate.java +++ b/src/main/java/org/qora/ApplyUpdate.java @@ -1,11 +1,13 @@ package org.qora; import java.io.IOException; +import java.lang.management.ManagementFactory; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.security.Security; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -42,7 +44,10 @@ public class ApplyUpdate { Security.insertProviderAt(new BouncyCastleJsseProvider(), 1); // Load/check settings, which potentially sets up blockchain config, etc. - Settings.getInstance(); + if (args.length > 0) + Settings.fileInstance(args[0]); + else + Settings.getInstance(); LOGGER.info("Applying update..."); @@ -54,7 +59,7 @@ public class ApplyUpdate { replaceJar(); // Restart node - restartNode(); + restartNode(args); LOGGER.info("Exiting..."); } @@ -122,7 +127,7 @@ public class ApplyUpdate { LOGGER.error("Failed to replace JAR - giving up"); } - private static void restartNode() { + private static void restartNode(String[] args) { String javaHome = System.getProperty("java.home"); LOGGER.info(String.format("Java home: %s", javaHome)); @@ -133,10 +138,23 @@ public class ApplyUpdate { LOGGER.info(String.format("Windows EXE launcher: %s", exeLauncher)); List javaCmd; - if (Files.exists(exeLauncher)) + if (Files.exists(exeLauncher)) { javaCmd = Arrays.asList(exeLauncher.toString()); - else - javaCmd = Arrays.asList(javaBinary.toString(), "-jar", JAR_FILENAME); + } else { + javaCmd = new ArrayList<>(); + // Java runtime binary itself + javaCmd.add(javaBinary.toString()); + + // JVM arguments + javaCmd.addAll(ManagementFactory.getRuntimeMXBean().getInputArguments()); + + // Call mainClass in JAR + javaCmd.addAll(Arrays.asList("-jar", JAR_FILENAME)); + + if (args.length > 0) + // Add settings filename + javaCmd.add(args[0]); + } try { LOGGER.info(String.format("Restarting node with: %s", String.join(" ", javaCmd))); diff --git a/src/main/java/org/qora/controller/AutoUpdate.java b/src/main/java/org/qora/controller/AutoUpdate.java index 610940bc..cdcad90c 100644 --- a/src/main/java/org/qora/controller/AutoUpdate.java +++ b/src/main/java/org/qora/controller/AutoUpdate.java @@ -4,12 +4,14 @@ import java.awt.TrayIcon.MessageType; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.lang.management.ManagementFactory; import java.nio.ByteBuffer; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -224,7 +226,21 @@ public class AutoUpdate extends Thread { LOGGER.debug(String.format("Java binary: %s", javaBinary)); try { - List javaCmd = Arrays.asList(javaBinary.toString(), "-cp", NEW_JAR_FILENAME, ApplyUpdate.class.getCanonicalName()); + List javaCmd = new ArrayList<>(); + // Java runtime binary itself + javaCmd.add(javaBinary.toString()); + + // JVM arguments + javaCmd.addAll(ManagementFactory.getRuntimeMXBean().getInputArguments()); + + // Call ApplyUpdate using new JAR + javaCmd.addAll(Arrays.asList("-cp", NEW_JAR_FILENAME, ApplyUpdate.class.getCanonicalName())); + + // Are we running with different settings? + String settingsFilename = Controller.getInstance().getSettingsFilename(); + if (settingsFilename != null) + javaCmd.add(settingsFilename); + LOGGER.info(String.format("Applying update with: %s", String.join(" ", javaCmd))); SysTray.getInstance().showMessage("Auto Update", "Applying automatic update and restarting...", MessageType.INFO); diff --git a/src/main/java/org/qora/controller/Controller.java b/src/main/java/org/qora/controller/Controller.java index f4404b72..d414a1be 100644 --- a/src/main/java/org/qora/controller/Controller.java +++ b/src/main/java/org/qora/controller/Controller.java @@ -121,6 +121,7 @@ public class Controller extends Thread { private final String buildVersion; private final long buildTimestamp; // seconds + private String settingsFilename; private AtomicReference chainTip = new AtomicReference<>(); @@ -229,6 +230,10 @@ public class Controller extends Thread { return this.blockchainLock; } + /* package */ String getSettingsFilename() { + return this.settingsFilename; + } + // Entry point public static void main(String[] args) { @@ -259,8 +264,16 @@ public class Controller extends Thread { RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(getRepositoryUrl()); RepositoryManager.setRepositoryFactory(repositoryFactory); } catch (DataException e) { - LOGGER.error("Unable to start repository", e); - System.exit(1); + // If exception has no cause then repository is in use by some other process. + if (e.getCause() == null) { + LOGGER.info("Repository in use by another process?"); + Gui.getInstance().fatalError("Repository issue", "Repository in use by another process?"); + } else { + LOGGER.error("Unable to start repository", e); + Gui.getInstance().fatalError("Repository issue", e); + } + + return; // Not System.exit() so that GUI can display error } LOGGER.info("Validating blockchain"); @@ -276,7 +289,8 @@ public class Controller extends Thread { } } catch (DataException e) { LOGGER.error("Couldn't validate blockchain", e); - System.exit(2); + Gui.getInstance().fatalError("Blockchain validation issue", e); + return; // Not System.exit() so that GUI can display error } LOGGER.info("Starting controller"); @@ -288,7 +302,8 @@ public class Controller extends Thread { network.start(); } catch (Exception e) { LOGGER.error("Unable to start networking", e); - System.exit(1); + Gui.getInstance().fatalError("Networking failure", e); + return; // Not System.exit() so that GUI can display error } Runtime.getRuntime().addShutdownHook(new Thread() { @@ -318,7 +333,8 @@ public class Controller extends Thread { apiService.start(); } catch (Exception e) { LOGGER.error("Unable to start API", e); - System.exit(1); + Gui.getInstance().fatalError("API failure", e); + return; // Not System.exit() so that GUI can display error } LOGGER.info(String.format("Starting node management UI on port %d", Settings.getInstance().getUiPort())); @@ -327,7 +343,8 @@ public class Controller extends Thread { uiService.start(); } catch (Exception e) { LOGGER.error("Unable to start node management UI", e); - System.exit(1); + Gui.getInstance().fatalError("Node management UI failure", e); + return; // Not System.exit() so that GUI can display error } // If GUI is enabled, we're no longer starting up but actually running now diff --git a/src/main/java/org/qora/gui/Gui.java b/src/main/java/org/qora/gui/Gui.java index 5244d6cb..864046cb 100644 --- a/src/main/java/org/qora/gui/Gui.java +++ b/src/main/java/org/qora/gui/Gui.java @@ -90,4 +90,12 @@ public class Gui { System.exit(0); } + public void fatalError(String title, Exception e) { + String message = e.getLocalizedMessage(); + if (e.getCause() != null && e.getCause().getLocalizedMessage() != null) + message += ": " + e.getCause().getLocalizedMessage(); + + this.fatalError(title, message); + } + } diff --git a/src/main/java/org/qora/network/Network.java b/src/main/java/org/qora/network/Network.java index ebdefa40..a56983a0 100644 --- a/src/main/java/org/qora/network/Network.java +++ b/src/main/java/org/qora/network/Network.java @@ -136,10 +136,10 @@ public class Network { serverChannel.register(channelSelector, SelectionKey.OP_ACCEPT); } catch (UnknownHostException e) { LOGGER.error(String.format("Can't bind listen socket to address %s", Settings.getInstance().getBindAddress())); - throw new RuntimeException("Can't bind listen socket to address"); + throw new RuntimeException("Can't bind listen socket to address", e); } catch (IOException e) { LOGGER.error(String.format("Can't create listen socket: %s", e.getMessage())); - throw new RuntimeException("Can't create listen socket"); + throw new RuntimeException("Can't create listen socket", e); } connectedPeers = new ArrayList<>(); diff --git a/src/main/java/org/qora/repository/hsqldb/HSQLDBRepositoryFactory.java b/src/main/java/org/qora/repository/hsqldb/HSQLDBRepositoryFactory.java index 7e65b606..229c3abe 100644 --- a/src/main/java/org/qora/repository/hsqldb/HSQLDBRepositoryFactory.java +++ b/src/main/java/org/qora/repository/hsqldb/HSQLDBRepositoryFactory.java @@ -26,6 +26,13 @@ public class HSQLDBRepositoryFactory implements RepositoryFactory { private String connectionUrl; private HSQLDBPool connectionPool; + /** + * Constructs new RepositoryFactory using passed connectionUrl. + * + * @param connectionUrl + * @throws DataException without throwable if repository in use by another process. + * @throws DataException with throwable if repository cannot be opened for someother reason. + */ public HSQLDBRepositoryFactory(String connectionUrl) throws DataException { // one-time initialization goes in here this.connectionUrl = connectionUrl; @@ -36,12 +43,15 @@ public class HSQLDBRepositoryFactory implements RepositoryFactory { } catch (SQLException e) { Throwable cause = e.getCause(); if (!(cause instanceof HsqlException)) - throw new DataException("Unable to open repository: " + e.getMessage()); + throw new DataException("Unable to open repository: " + e.getMessage(), e); HsqlException he = (HsqlException) cause; - if (he.getErrorCode() != -ErrorCode.ERROR_IN_LOG_FILE && he.getErrorCode() != -ErrorCode.M_DatabaseScriptReader_read) + if (he.getErrorCode() == -ErrorCode.LOCK_FILE_ACQUISITION_FAILURE) throw new DataException("Unable to open repository: " + e.getMessage()); + if (he.getErrorCode() != -ErrorCode.ERROR_IN_LOG_FILE && he.getErrorCode() != -ErrorCode.M_DatabaseScriptReader_read) + throw new DataException("Unable to open repository: " + e.getMessage(), e); + // Attempt recovery? HSQLDBRepository.attemptRecovery(connectionUrl); }