Propagate JVM arguments through auto-update. Improve start-up error messages shown by GUI

This commit is contained in:
catbref 2019-11-28 11:57:26 +00:00
parent 339e757d34
commit 62ed4e322b
6 changed files with 86 additions and 17 deletions

View File

@ -1,11 +1,13 @@
package org.qora; package org.qora;
import java.io.IOException; import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
import java.security.Security; import java.security.Security;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@ -42,7 +44,10 @@ public class ApplyUpdate {
Security.insertProviderAt(new BouncyCastleJsseProvider(), 1); Security.insertProviderAt(new BouncyCastleJsseProvider(), 1);
// Load/check settings, which potentially sets up blockchain config, etc. // 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..."); LOGGER.info("Applying update...");
@ -54,7 +59,7 @@ public class ApplyUpdate {
replaceJar(); replaceJar();
// Restart node // Restart node
restartNode(); restartNode(args);
LOGGER.info("Exiting..."); LOGGER.info("Exiting...");
} }
@ -122,7 +127,7 @@ public class ApplyUpdate {
LOGGER.error("Failed to replace JAR - giving up"); LOGGER.error("Failed to replace JAR - giving up");
} }
private static void restartNode() { private static void restartNode(String[] args) {
String javaHome = System.getProperty("java.home"); String javaHome = System.getProperty("java.home");
LOGGER.info(String.format("Java home: %s", javaHome)); LOGGER.info(String.format("Java home: %s", javaHome));
@ -133,10 +138,23 @@ public class ApplyUpdate {
LOGGER.info(String.format("Windows EXE launcher: %s", exeLauncher)); LOGGER.info(String.format("Windows EXE launcher: %s", exeLauncher));
List<String> javaCmd; List<String> javaCmd;
if (Files.exists(exeLauncher)) if (Files.exists(exeLauncher)) {
javaCmd = Arrays.asList(exeLauncher.toString()); javaCmd = Arrays.asList(exeLauncher.toString());
else } else {
javaCmd = Arrays.asList(javaBinary.toString(), "-jar", JAR_FILENAME); 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 { try {
LOGGER.info(String.format("Restarting node with: %s", String.join(" ", javaCmd))); LOGGER.info(String.format("Restarting node with: %s", String.join(" ", javaCmd)));

View File

@ -4,12 +4,14 @@ import java.awt.TrayIcon.MessageType;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.lang.management.ManagementFactory;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@ -224,7 +226,21 @@ public class AutoUpdate extends Thread {
LOGGER.debug(String.format("Java binary: %s", javaBinary)); LOGGER.debug(String.format("Java binary: %s", javaBinary));
try { try {
List<String> javaCmd = Arrays.asList(javaBinary.toString(), "-cp", NEW_JAR_FILENAME, ApplyUpdate.class.getCanonicalName()); List<String> 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))); LOGGER.info(String.format("Applying update with: %s", String.join(" ", javaCmd)));
SysTray.getInstance().showMessage("Auto Update", "Applying automatic update and restarting...", MessageType.INFO); SysTray.getInstance().showMessage("Auto Update", "Applying automatic update and restarting...", MessageType.INFO);

View File

@ -121,6 +121,7 @@ public class Controller extends Thread {
private final String buildVersion; private final String buildVersion;
private final long buildTimestamp; // seconds private final long buildTimestamp; // seconds
private String settingsFilename;
private AtomicReference<BlockData> chainTip = new AtomicReference<>(); private AtomicReference<BlockData> chainTip = new AtomicReference<>();
@ -229,6 +230,10 @@ public class Controller extends Thread {
return this.blockchainLock; return this.blockchainLock;
} }
/* package */ String getSettingsFilename() {
return this.settingsFilename;
}
// Entry point // Entry point
public static void main(String[] args) { public static void main(String[] args) {
@ -259,8 +264,16 @@ public class Controller extends Thread {
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(getRepositoryUrl()); RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(getRepositoryUrl());
RepositoryManager.setRepositoryFactory(repositoryFactory); RepositoryManager.setRepositoryFactory(repositoryFactory);
} catch (DataException e) { } catch (DataException e) {
LOGGER.error("Unable to start repository", e); // If exception has no cause then repository is in use by some other process.
System.exit(1); 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"); LOGGER.info("Validating blockchain");
@ -276,7 +289,8 @@ public class Controller extends Thread {
} }
} catch (DataException e) { } catch (DataException e) {
LOGGER.error("Couldn't validate blockchain", 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"); LOGGER.info("Starting controller");
@ -288,7 +302,8 @@ public class Controller extends Thread {
network.start(); network.start();
} catch (Exception e) { } catch (Exception e) {
LOGGER.error("Unable to start networking", 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() { Runtime.getRuntime().addShutdownHook(new Thread() {
@ -318,7 +333,8 @@ public class Controller extends Thread {
apiService.start(); apiService.start();
} catch (Exception e) { } catch (Exception e) {
LOGGER.error("Unable to start API", 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())); 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(); uiService.start();
} catch (Exception e) { } catch (Exception e) {
LOGGER.error("Unable to start node management UI", 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 // If GUI is enabled, we're no longer starting up but actually running now

View File

@ -90,4 +90,12 @@ public class Gui {
System.exit(0); 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);
}
} }

View File

@ -136,10 +136,10 @@ public class Network {
serverChannel.register(channelSelector, SelectionKey.OP_ACCEPT); serverChannel.register(channelSelector, SelectionKey.OP_ACCEPT);
} catch (UnknownHostException e) { } catch (UnknownHostException e) {
LOGGER.error(String.format("Can't bind listen socket to address %s", Settings.getInstance().getBindAddress())); 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) { } catch (IOException e) {
LOGGER.error(String.format("Can't create listen socket: %s", e.getMessage())); 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<>(); connectedPeers = new ArrayList<>();

View File

@ -26,6 +26,13 @@ public class HSQLDBRepositoryFactory implements RepositoryFactory {
private String connectionUrl; private String connectionUrl;
private HSQLDBPool connectionPool; private HSQLDBPool connectionPool;
/**
* Constructs new RepositoryFactory using passed <tt>connectionUrl</tt>.
*
* @param connectionUrl
* @throws DataException <i>without throwable</i> if repository in use by another process.
* @throws DataException <i>with throwable</i> if repository cannot be opened for someother reason.
*/
public HSQLDBRepositoryFactory(String connectionUrl) throws DataException { public HSQLDBRepositoryFactory(String connectionUrl) throws DataException {
// one-time initialization goes in here // one-time initialization goes in here
this.connectionUrl = connectionUrl; this.connectionUrl = connectionUrl;
@ -36,12 +43,15 @@ public class HSQLDBRepositoryFactory implements RepositoryFactory {
} catch (SQLException e) { } catch (SQLException e) {
Throwable cause = e.getCause(); Throwable cause = e.getCause();
if (!(cause instanceof HsqlException)) 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; 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()); 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? // Attempt recovery?
HSQLDBRepository.attemptRecovery(connectionUrl); HSQLDBRepository.attemptRecovery(connectionUrl);
} }