From 9877ba87a4fd6f5da2bd4e75c7695abcfd474ed2 Mon Sep 17 00:00:00 2001 From: woodser Date: Thu, 19 Jan 2023 19:19:56 -0500 Subject: [PATCH] delete and restore account restarts application automatically added standard input to keepalive loop for issuing commands to daemon Co-authored-by: duriancrepe --- .../persistence/PersistenceManager.java | 9 ++ .../common/setup/GracefulShutDownHandler.java | 1 + .../bisq/core/api/CoreAccountService.java | 28 +++- .../java/bisq/core}/app/ConsoleInput.java | 2 +- .../bisq/core}/app/ConsoleInputReadTask.java | 2 +- .../java/bisq/core/app/HavenoExecutable.java | 123 ++++++++++++++---- .../bisq/core/app/HavenoHeadlessAppMain.java | 14 +- .../app/misc/ExecutableForAppWithP2p.java | 12 +- .../btc/setup/MoneroWalletRpcManager.java | 6 +- .../bisq/core/btc/setup/WalletConfig.java | 1 - .../core/btc/wallet/XmrWalletService.java | 10 +- .../bisq/daemon/app/HavenoDaemonMain.java | 38 +++++- .../main/java/bisq/seednode/SeedNodeMain.java | 4 +- .../java/bisq/statistics/StatisticsMain.java | 4 +- 14 files changed, 181 insertions(+), 73 deletions(-) rename {daemon/src/main/java/bisq/daemon => core/src/main/java/bisq/core}/app/ConsoleInput.java (98%) rename {daemon/src/main/java/bisq/daemon => core/src/main/java/bisq/core}/app/ConsoleInputReadTask.java (98%) diff --git a/common/src/main/java/bisq/common/persistence/PersistenceManager.java b/common/src/main/java/bisq/common/persistence/PersistenceManager.java index 55e9aabe5c..6b3de1a567 100644 --- a/common/src/main/java/bisq/common/persistence/PersistenceManager.java +++ b/common/src/main/java/bisq/common/persistence/PersistenceManager.java @@ -108,6 +108,15 @@ public class PersistenceManager { flushAllDataToDisk(completeHandler, true); } + /** + * Resets the static members of PersistenceManager to restart the application. + */ + public static void reset() { + ALL_PERSISTENCE_MANAGERS.clear(); + flushAtShutdownCalled = false; + allServicesInitialized.set(false); + } + // We require being called only once from the global shutdown routine. As the shutdown routine has a timeout // and error condition where we call the method as well beside the standard path and it could be that those // alternative code paths call our method after it was called already, so it is a valid but rare case. diff --git a/common/src/main/java/bisq/common/setup/GracefulShutDownHandler.java b/common/src/main/java/bisq/common/setup/GracefulShutDownHandler.java index c93bd0dbde..485791b5d7 100644 --- a/common/src/main/java/bisq/common/setup/GracefulShutDownHandler.java +++ b/common/src/main/java/bisq/common/setup/GracefulShutDownHandler.java @@ -21,4 +21,5 @@ import bisq.common.handlers.ResultHandler; public interface GracefulShutDownHandler { void gracefulShutDown(ResultHandler resultHandler); + void gracefulShutDown(ResultHandler resultHandler, boolean systemExit); } diff --git a/core/src/main/java/bisq/core/api/CoreAccountService.java b/core/src/main/java/bisq/core/api/CoreAccountService.java index e0c9b9eceb..3133ce31a3 100644 --- a/core/src/main/java/bisq/core/api/CoreAccountService.java +++ b/core/src/main/java/bisq/core/api/CoreAccountService.java @@ -69,11 +69,15 @@ public class CoreAccountService { } public void addListener(AccountServiceListener listener) { - listeners.add(listener); + synchronized (listeners) { + listeners.add(listener); + } } public boolean removeListener(AccountServiceListener listener) { - return listeners.remove(listener); + synchronized (listeners) { + return listeners.remove(listener); + } } public boolean accountExists() { @@ -99,7 +103,9 @@ public class CoreAccountService { if (!accountExists()) throw new IllegalStateException("Cannot open account if account does not exist"); if (keyRing.unlockKeys(password, false)) { this.password = password; - for (AccountServiceListener listener : new ArrayList(listeners)) listener.onAccountOpened(); + synchronized (listeners) { + for (AccountServiceListener listener : listeners) listener.onAccountOpened(); + } } else { throw new IllegalStateException("keyRing.unlockKeys() returned false, that should never happen"); } @@ -110,13 +116,17 @@ public class CoreAccountService { String oldPassword = this.password; keyStorage.saveKeyRing(keyRing, oldPassword, password); this.password = password; - for (AccountServiceListener listener : new ArrayList(listeners)) listener.onPasswordChanged(oldPassword, password); + synchronized (listeners) { + for (AccountServiceListener listener : listeners) listener.onPasswordChanged(oldPassword, password); + } } public void closeAccount() { if (!isAccountOpen()) throw new IllegalStateException("Cannot close unopened account"); keyRing.lockKeys(); // closed account means the keys are locked - for (AccountServiceListener listener : new ArrayList(listeners)) listener.onAccountClosed(); + synchronized (listeners) { + for (AccountServiceListener listener : listeners) listener.onAccountClosed(); + } } public void backupAccount(int bufferSize, Consumer consume, Consumer error) { @@ -147,13 +157,17 @@ public class CoreAccountService { if (accountExists()) throw new IllegalStateException("Cannot restore account if there is an existing account"); File dataDir = new File(config.appDataDir.getPath()); ZipUtils.unzipToDir(dataDir, inputStream, bufferSize); - for (AccountServiceListener listener : listeners) listener.onAccountRestored(onShutdown); + synchronized (listeners) { + for (AccountServiceListener listener : listeners) listener.onAccountRestored(onShutdown); + } } public void deleteAccount(Runnable onShutdown) { try { keyRing.lockKeys(); - for (AccountServiceListener listener : listeners) listener.onAccountDeleted(onShutdown); + synchronized (listeners) { + for (AccountServiceListener listener : listeners) listener.onAccountDeleted(onShutdown); + } File dataDir = new File(config.appDataDir.getPath()); // TODO (woodser): deleting directory after gracefulShutdown() so services don't throw when they try to persist (e.g. XmrTxProofService), but gracefulShutdown() should honor read-only shutdown FileUtil.deleteDirectory(dataDir, null, false); } catch (Exception err) { diff --git a/daemon/src/main/java/bisq/daemon/app/ConsoleInput.java b/core/src/main/java/bisq/core/app/ConsoleInput.java similarity index 98% rename from daemon/src/main/java/bisq/daemon/app/ConsoleInput.java rename to core/src/main/java/bisq/core/app/ConsoleInput.java index 18bca675c8..a3aadad7d0 100644 --- a/daemon/src/main/java/bisq/daemon/app/ConsoleInput.java +++ b/core/src/main/java/bisq/core/app/ConsoleInput.java @@ -14,7 +14,7 @@ * You should have received a copy of the GNU Affero General Public License * along with Haveno. If not, see . */ -package bisq.daemon.app; +package bisq.core.app; import java.util.concurrent.*; diff --git a/daemon/src/main/java/bisq/daemon/app/ConsoleInputReadTask.java b/core/src/main/java/bisq/core/app/ConsoleInputReadTask.java similarity index 98% rename from daemon/src/main/java/bisq/daemon/app/ConsoleInputReadTask.java rename to core/src/main/java/bisq/core/app/ConsoleInputReadTask.java index 4bfb2840b8..b4a263ce95 100644 --- a/daemon/src/main/java/bisq/daemon/app/ConsoleInputReadTask.java +++ b/core/src/main/java/bisq/core/app/ConsoleInputReadTask.java @@ -14,7 +14,7 @@ * You should have received a copy of the GNU Affero General Public License * along with Haveno. If not, see . */ -package bisq.daemon.app; +package bisq.core.app; import java.io.*; import java.util.concurrent.Callable; diff --git a/core/src/main/java/bisq/core/app/HavenoExecutable.java b/core/src/main/java/bisq/core/app/HavenoExecutable.java index 1333d1ca69..1935bdb461 100644 --- a/core/src/main/java/bisq/core/app/HavenoExecutable.java +++ b/core/src/main/java/bisq/core/app/HavenoExecutable.java @@ -19,6 +19,7 @@ package bisq.core.app; import bisq.core.api.AccountServiceListener; import bisq.core.api.CoreAccountService; +import bisq.core.api.CoreMoneroConnectionsService; import bisq.core.btc.setup.WalletsSetup; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.XmrWalletService; @@ -48,7 +49,11 @@ import bisq.common.util.Utilities; import com.google.inject.Guice; import com.google.inject.Injector; + +import java.io.Console; + import java.util.List; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import lombok.extern.slf4j.Slf4j; @@ -60,6 +65,7 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven public static final int EXIT_SUCCESS = 0; public static final int EXIT_FAILURE = 1; + public static final int EXIT_RESTART = 2; private final String fullName; private final String scriptName; @@ -72,6 +78,9 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven protected Config config; private boolean isShutdownInProgress; private boolean isReadOnly; + private Thread keepRunningThread; + private AtomicInteger keepRunningResult = new AtomicInteger(EXIT_SUCCESS); + private Runnable shutdownCompletedHandler; public HavenoExecutable(String fullName, String scriptName, String appName, String version) { this.fullName = fullName; @@ -80,7 +89,7 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven this.version = version; } - public void execute(String[] args) { + public int execute(String[] args) { try { config = new Config(appName, Utilities.getUserDataDir(), args); if (config.helpRequested) { @@ -98,14 +107,14 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven System.exit(EXIT_FAILURE); } - doExecute(); + return doExecute(); } /////////////////////////////////////////////////////////////////////////////////////////// // First synchronous execution tasks /////////////////////////////////////////////////////////////////////////////////////////// - protected void doExecute() { + protected int doExecute() { CommonSetup.setup(config, this); CoreSetup.setup(config); @@ -113,6 +122,8 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven // If application is JavaFX application we need to wait until it is initialized launchApplication(); + + return EXIT_SUCCESS; } protected abstract void configUserThread(); @@ -149,8 +160,8 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven // Application needs to restart on delete and restore of account. accountService.addListener(new AccountServiceListener() { - @Override public void onAccountDeleted(Runnable onShutdown) { shutDownNoPersist(onShutdown); } - @Override public void onAccountRestored(Runnable onShutdown) { shutDownNoPersist(onShutdown); } + @Override public void onAccountDeleted(Runnable onShutdown) { shutDownNoPersist(onShutdown, true); } + @Override public void onAccountRestored(Runnable onShutdown) { shutDownNoPersist(onShutdown, true); } }); // Attempt to login, subclasses should implement interactive login and or rpc login. @@ -165,18 +176,27 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven /** * Do not persist when shutting down after account restore and restarts since * that causes the current persistables to overwrite the restored or deleted state. + * + * If restart is specified, initiates an in-process asynchronous restart of the + * application by interrupting the keepRunningThread. */ - protected void shutDownNoPersist(Runnable onShutdown) { + protected void shutDownNoPersist(Runnable onShutdown, boolean restart) { this.isReadOnly = true; - gracefulShutDown(() -> { - log.info("Shutdown without persisting"); - if (onShutdown != null) onShutdown.run(); - }); + if (restart) { + shutdownCompletedHandler = onShutdown; + keepRunningResult.set(EXIT_RESTART); + keepRunningThread.interrupt(); + } else { + gracefulShutDown(() -> { + log.info("Shutdown without persisting"); + if (onShutdown != null) onShutdown.run(); + }); + } } /** * Attempt to login. TODO: supply a password in config or args - * + * * @return true if account is opened successfully. */ protected boolean loginAccount() { @@ -258,16 +278,32 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven // GracefulShutDownHandler implementation /////////////////////////////////////////////////////////////////////////////////////////// - // This might need to be overwritten in case the application is not using all modules @Override public void gracefulShutDown(ResultHandler resultHandler) { + gracefulShutDown(resultHandler, true); + } + + // This might need to be overwritten in case the application is not using all modules + @Override + public void gracefulShutDown(ResultHandler onShutdown, boolean systemExit) { log.info("Start graceful shutDown"); if (isShutdownInProgress) { + log.info("Ignoring call to gracefulShutDown, already in progress"); return; } isShutdownInProgress = true; + ResultHandler resultHandler; + if (shutdownCompletedHandler != null) { + resultHandler = () -> { + shutdownCompletedHandler.run(); + onShutdown.handleResult(); + }; + } else { + resultHandler = onShutdown; + } + if (injector == null) { log.info("Shut down called before injector was created"); resultHandler.handleResult(); @@ -281,7 +317,7 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven injector.getInstance(XmrTxProofService.class).shutDown(); injector.getInstance(AvoidStandbyModeService.class).shutDown(); injector.getInstance(TradeManager.class).shutDown(); - injector.getInstance(XmrWalletService.class).shutDown(); // TODO: why not shut down BtcWalletService, etc? shutdown CoreMoneroConnectionsService + injector.getInstance(XmrWalletService.class).shutDown(!isReadOnly); // TODO: why not shut down BtcWalletService, etc? shutdown CoreMoneroConnectionsService log.info("OpenOfferManager shutdown started"); injector.getInstance(OpenOfferManager.class).shutDown(() -> { log.info("OpenOfferManager shutdown completed"); @@ -296,36 +332,31 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven injector.getInstance(P2PService.class).shutDown(() -> { log.info("P2PService shutdown completed"); module.close(injector); - completeShutdown(resultHandler, EXIT_SUCCESS); + completeShutdown(resultHandler, EXIT_SUCCESS, systemExit); }); }); walletsSetup.shutDown(); - }); - - // Wait max 20 sec. - UserThread.runAfter(() -> { - log.warn("Graceful shut down not completed in 20 sec. We trigger our timeout handler."); - completeShutdown(resultHandler, EXIT_SUCCESS); - }, 20); } catch (Throwable t) { log.error("App shutdown failed with exception {}", t.toString()); t.printStackTrace(); - completeShutdown(resultHandler, EXIT_FAILURE); + completeShutdown(resultHandler, EXIT_FAILURE, systemExit); } } - private void completeShutdown(ResultHandler resultHandler, int exitCode) { + private void completeShutdown(ResultHandler resultHandler, int exitCode, boolean systemExit) { if (!isReadOnly) { // If user tried to downgrade we do not write the persistable data to avoid data corruption PersistenceManager.flushAllDataToDiskAtShutdown(() -> { log.info("Graceful shutdown flushed persistence. Exiting now."); resultHandler.handleResult(); - UserThread.runAfter(() -> System.exit(exitCode), 1); + if (systemExit) + UserThread.runAfter(() -> System.exit(exitCode), 1); }); } else { resultHandler.handleResult(); - UserThread.runAfter(() -> System.exit(exitCode), 1); + if (systemExit) + UserThread.runAfter(() -> System.exit(exitCode), 1); } } @@ -341,4 +372,46 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven if (doShutDown) gracefulShutDown(() -> log.info("gracefulShutDown complete")); } + + /** + * Runs until a command interrupts the application and returns the desired command behavior. + * @return EXIT_SUCCESS to initiate a shutdown, EXIT_RESTART to initiate an in process restart. + */ + protected int keepRunning() { + keepRunningThread = new Thread(() -> { + ConsoleInput reader = new ConsoleInput(Integer.MAX_VALUE, Integer.MAX_VALUE, TimeUnit.MILLISECONDS); + while (true) { + Console console = System.console(); + try { + if (console == null) { + Thread.sleep(Long.MAX_VALUE); + } else { + var cmd = reader.readLine(); + if ("exit".equals(cmd)) { + keepRunningResult.set(EXIT_SUCCESS); + break; + } else if ("restart".equals(cmd)) { + keepRunningResult.set(EXIT_RESTART); + break; + } else if ("help".equals(cmd)) { + System.out.println("Commands: restart, exit, help"); + } else { + System.out.println("Unknown command, use: restart, exit, help"); + } + } + } catch (InterruptedException e) { + break; + } + } + }); + + keepRunningThread.start(); + try { + keepRunningThread.join(); + } catch (InterruptedException ie) { + System.out.println(ie); + } + + return keepRunningResult.get(); + } } diff --git a/core/src/main/java/bisq/core/app/HavenoHeadlessAppMain.java b/core/src/main/java/bisq/core/app/HavenoHeadlessAppMain.java index da6335ef2c..b2e7da4136 100644 --- a/core/src/main/java/bisq/core/app/HavenoHeadlessAppMain.java +++ b/core/src/main/java/bisq/core/app/HavenoHeadlessAppMain.java @@ -47,10 +47,10 @@ public class HavenoHeadlessAppMain extends HavenoExecutable { } @Override - protected void doExecute() { + protected int doExecute() { super.doExecute(); - keepRunning(); + return keepRunning(); } /////////////////////////////////////////////////////////////////////////////////////////// @@ -114,14 +114,4 @@ public class HavenoHeadlessAppMain extends HavenoExecutable { // In headless mode we don't have an async behaviour so we trigger the setup by calling onApplicationStarted onApplicationStarted(); } - - // TODO: implement interactive console which allows user to input commands; login, logoff, exit - private void keepRunning() { - while (true) { - try { - Thread.sleep(Long.MAX_VALUE); - } catch (InterruptedException ignore) { - } - } - } } diff --git a/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java b/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java index a9fbab9b03..bc7b598dc4 100644 --- a/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java +++ b/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java @@ -97,7 +97,7 @@ public abstract class ExecutableForAppWithP2p extends HavenoExecutable { }); }); injector.getInstance(WalletsSetup.class).shutDown(); - injector.getInstance(XmrWalletService.class).shutDown(); // TODO (woodser): this is not actually called, perhaps because WalletsSetup.class completes too quick so its listener calls System.exit(0) + injector.getInstance(XmrWalletService.class).shutDown(true); // TODO (woodser): this is not actually called, perhaps because WalletsSetup.class completes too quick so its listener calls System.exit(0) injector.getInstance(BtcWalletService.class).shutDown(); })); // we wait max 5 sec. @@ -187,16 +187,6 @@ public abstract class ExecutableForAppWithP2p extends HavenoExecutable { }, TimeUnit.HOURS.toSeconds(2)); } - @SuppressWarnings("InfiniteLoopStatement") - protected void keepRunning() { - while (true) { - try { - Thread.sleep(Long.MAX_VALUE); - } catch (InterruptedException ignore) { - } - } - } - protected void checkMemory(Config config, GracefulShutDownHandler gracefulShutDownHandler) { int maxMemory = config.maxMemory; UserThread.runPeriodically(() -> { diff --git a/core/src/main/java/bisq/core/btc/setup/MoneroWalletRpcManager.java b/core/src/main/java/bisq/core/btc/setup/MoneroWalletRpcManager.java index df82d047dc..d1f4736ad8 100644 --- a/core/src/main/java/bisq/core/btc/setup/MoneroWalletRpcManager.java +++ b/core/src/main/java/bisq/core/btc/setup/MoneroWalletRpcManager.java @@ -113,13 +113,15 @@ public class MoneroWalletRpcManager { public void stopInstance(MoneroWalletRpc walletRpc) { // unregister port + int port = -1; synchronized (registeredPorts) { boolean found = false; for (Map.Entry entry : registeredPorts.entrySet()) { if (walletRpc == entry.getValue()) { found = true; try { - unregisterPort(entry.getKey()); + port = entry.getKey(); + unregisterPort(port); } catch (Exception e) { throw new MoneroError(e); } @@ -130,6 +132,8 @@ public class MoneroWalletRpcManager { } // stop process + String pid = walletRpc.getProcess() == null ? null : String.valueOf(walletRpc.getProcess().pid()); + log.info("Stopping MoneroWalletRpc port: {} pid: {}", port, pid); walletRpc.stopProcess(); } diff --git a/core/src/main/java/bisq/core/btc/setup/WalletConfig.java b/core/src/main/java/bisq/core/btc/setup/WalletConfig.java index f6f193fc9e..6471f45a22 100644 --- a/core/src/main/java/bisq/core/btc/setup/WalletConfig.java +++ b/core/src/main/java/bisq/core/btc/setup/WalletConfig.java @@ -252,7 +252,6 @@ public class WalletConfig extends AbstractIdleService { @Override protected void shutDown() throws Exception { - } public NetworkParameters params() { diff --git a/core/src/main/java/bisq/core/btc/wallet/XmrWalletService.java b/core/src/main/java/bisq/core/btc/wallet/XmrWalletService.java index edc52281f4..0f0067f26a 100644 --- a/core/src/main/java/bisq/core/btc/wallet/XmrWalletService.java +++ b/core/src/main/java/bisq/core/btc/wallet/XmrWalletService.java @@ -146,7 +146,7 @@ public class XmrWalletService { @Override public void onAccountClosed() { log.info(getClass() + ".accountService.onAccountClosed()"); - closeAllWallets(); + closeAllWallets(true); } @Override @@ -304,7 +304,7 @@ public class XmrWalletService { /** * Thaw the given outputs with a lock on the wallet. - * + * * @param keyImages the key images to thaw */ public void thawOutputs(Collection keyImages) { @@ -527,9 +527,9 @@ public class XmrWalletService { } } - public void shutDown() { + public void shutDown(boolean save) { this.isShutDown = true; - closeAllWallets(); + closeAllWallets(save); } // ------------------------------ PRIVATE HELPERS ------------------------- @@ -746,7 +746,7 @@ public class XmrWalletService { if (!new File(path + ".address.txt").delete()) throw new RuntimeException("Failed to delete wallet file: " + path); } - private void closeAllWallets() { + private void closeAllWallets(boolean save) { // collect wallets to shutdown List openWallets = new ArrayList(); diff --git a/daemon/src/main/java/bisq/daemon/app/HavenoDaemonMain.java b/daemon/src/main/java/bisq/daemon/app/HavenoDaemonMain.java index 2e1185f5c4..e58ed5aa9e 100644 --- a/daemon/src/main/java/bisq/daemon/app/HavenoDaemonMain.java +++ b/daemon/src/main/java/bisq/daemon/app/HavenoDaemonMain.java @@ -17,6 +17,7 @@ package bisq.daemon.app; +import bisq.core.app.ConsoleInput; import bisq.core.app.HavenoHeadlessAppMain; import bisq.core.app.HavenoSetup; import bisq.core.api.AccountServiceListener; @@ -26,6 +27,7 @@ import bisq.common.UserThread; import bisq.common.app.AppModule; import bisq.common.crypto.IncorrectPasswordException; import bisq.common.handlers.ResultHandler; +import bisq.common.persistence.PersistenceManager; import com.google.common.util.concurrent.ThreadFactoryBuilder; @@ -34,6 +36,7 @@ import java.util.concurrent.CancellationException; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import lombok.extern.slf4j.Slf4j; @@ -45,7 +48,28 @@ public class HavenoDaemonMain extends HavenoHeadlessAppMain implements HavenoSet private GrpcServer grpcServer; public static void main(String[] args) { - new HavenoDaemonMain().execute(args); + var keepRunning = true; + while (keepRunning) { + keepRunning = false; + var daemon = new HavenoDaemonMain(); + var ret = daemon.execute(args); + if (ret == EXIT_SUCCESS) { + UserThread.execute(() -> daemon.gracefulShutDown(() -> {})); + } else if (ret == EXIT_RESTART) { + AtomicBoolean shuttingDown = new AtomicBoolean(true); + UserThread.execute(() -> daemon.gracefulShutDown(() -> shuttingDown.set(false), false)); + keepRunning = true; + // wait for graceful shutdown + try { + while (shuttingDown.get()) { + Thread.sleep(1000); + } + PersistenceManager.reset(); + } catch (InterruptedException e) { + System.out.println("interrupted!"); + } + } + } } ///////////////////////////////////////////////////////////////////////////////////// @@ -106,8 +130,8 @@ public class HavenoDaemonMain extends HavenoHeadlessAppMain implements HavenoSet } @Override - public void gracefulShutDown(ResultHandler resultHandler) { - super.gracefulShutDown(resultHandler); + public void gracefulShutDown(ResultHandler resultHandler, boolean exit) { + super.gracefulShutDown(resultHandler, exit); if (grpcServer != null) grpcServer.shutdown(); // could be null if application attempted to shutdown early } @@ -120,7 +144,6 @@ public class HavenoDaemonMain extends HavenoHeadlessAppMain implements HavenoSet // Start rpc server in case login is coming in from rpc grpcServer = injector.getInstance(GrpcServer.class); - grpcServer.start(); if (!opened) { // Nonblocking, we need to stop if the login occurred through rpc. @@ -129,7 +152,6 @@ public class HavenoDaemonMain extends HavenoHeadlessAppMain implements HavenoSet Thread t = new Thread(() -> { interactiveLogin(reader); }); - t.start(); // Handle asynchronous account opens. // Will need to also close and reopen account. @@ -143,10 +165,14 @@ public class HavenoDaemonMain extends HavenoHeadlessAppMain implements HavenoSet }; accountService.addListener(accountListener); + // start server after the listener is registered + grpcServer.start(); + try { // Wait until interactive login or rpc. Check one more time if account is open to close race condition. if (!accountService.isAccountOpen()) { log.info("Interactive login required"); + t.start(); t.join(); } } catch (InterruptedException e) { @@ -155,6 +181,8 @@ public class HavenoDaemonMain extends HavenoHeadlessAppMain implements HavenoSet accountService.removeListener(accountListener); opened = accountService.isAccountOpen(); + } else { + grpcServer.start(); } return opened; diff --git a/seednode/src/main/java/bisq/seednode/SeedNodeMain.java b/seednode/src/main/java/bisq/seednode/SeedNodeMain.java index d4d30e01dd..f675f8b5f3 100644 --- a/seednode/src/main/java/bisq/seednode/SeedNodeMain.java +++ b/seednode/src/main/java/bisq/seednode/SeedNodeMain.java @@ -60,12 +60,12 @@ public class SeedNodeMain extends ExecutableForAppWithP2p { } @Override - protected void doExecute() { + protected int doExecute() { super.doExecute(); checkMemory(config, this); - keepRunning(); + return keepRunning(); } @Override diff --git a/statsnode/src/main/java/bisq/statistics/StatisticsMain.java b/statsnode/src/main/java/bisq/statistics/StatisticsMain.java index e52c7b7b4b..f0e4af3c40 100644 --- a/statsnode/src/main/java/bisq/statistics/StatisticsMain.java +++ b/statsnode/src/main/java/bisq/statistics/StatisticsMain.java @@ -40,12 +40,12 @@ public class StatisticsMain extends ExecutableForAppWithP2p { } @Override - protected void doExecute() { + protected int doExecute() { super.doExecute(); checkMemory(config, this); - keepRunning(); + return keepRunning(); } @Override