mirror of
https://github.com/boldsuck/haveno.git
synced 2024-12-22 20:19:21 +00:00
delete and restore account restarts application automatically
added standard input to keepalive loop for issuing commands to daemon Co-authored-by: duriancrepe <duriancrepe@protonmail.com>
This commit is contained in:
parent
350d2d5398
commit
9877ba87a4
14 changed files with 181 additions and 73 deletions
|
@ -108,6 +108,15 @@ public class PersistenceManager<T extends PersistableEnvelope> {
|
||||||
flushAllDataToDisk(completeHandler, true);
|
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
|
// 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
|
// 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.
|
// alternative code paths call our method after it was called already, so it is a valid but rare case.
|
||||||
|
|
|
@ -21,4 +21,5 @@ import bisq.common.handlers.ResultHandler;
|
||||||
|
|
||||||
public interface GracefulShutDownHandler {
|
public interface GracefulShutDownHandler {
|
||||||
void gracefulShutDown(ResultHandler resultHandler);
|
void gracefulShutDown(ResultHandler resultHandler);
|
||||||
|
void gracefulShutDown(ResultHandler resultHandler, boolean systemExit);
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,11 +69,15 @@ public class CoreAccountService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addListener(AccountServiceListener listener) {
|
public void addListener(AccountServiceListener listener) {
|
||||||
listeners.add(listener);
|
synchronized (listeners) {
|
||||||
|
listeners.add(listener);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean removeListener(AccountServiceListener listener) {
|
public boolean removeListener(AccountServiceListener listener) {
|
||||||
return listeners.remove(listener);
|
synchronized (listeners) {
|
||||||
|
return listeners.remove(listener);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean accountExists() {
|
public boolean accountExists() {
|
||||||
|
@ -99,7 +103,9 @@ public class CoreAccountService {
|
||||||
if (!accountExists()) throw new IllegalStateException("Cannot open account if account does not exist");
|
if (!accountExists()) throw new IllegalStateException("Cannot open account if account does not exist");
|
||||||
if (keyRing.unlockKeys(password, false)) {
|
if (keyRing.unlockKeys(password, false)) {
|
||||||
this.password = password;
|
this.password = password;
|
||||||
for (AccountServiceListener listener : new ArrayList<AccountServiceListener>(listeners)) listener.onAccountOpened();
|
synchronized (listeners) {
|
||||||
|
for (AccountServiceListener listener : listeners) listener.onAccountOpened();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalStateException("keyRing.unlockKeys() returned false, that should never happen");
|
throw new IllegalStateException("keyRing.unlockKeys() returned false, that should never happen");
|
||||||
}
|
}
|
||||||
|
@ -110,13 +116,17 @@ public class CoreAccountService {
|
||||||
String oldPassword = this.password;
|
String oldPassword = this.password;
|
||||||
keyStorage.saveKeyRing(keyRing, oldPassword, password);
|
keyStorage.saveKeyRing(keyRing, oldPassword, password);
|
||||||
this.password = password;
|
this.password = password;
|
||||||
for (AccountServiceListener listener : new ArrayList<AccountServiceListener>(listeners)) listener.onPasswordChanged(oldPassword, password);
|
synchronized (listeners) {
|
||||||
|
for (AccountServiceListener listener : listeners) listener.onPasswordChanged(oldPassword, password);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void closeAccount() {
|
public void closeAccount() {
|
||||||
if (!isAccountOpen()) throw new IllegalStateException("Cannot close unopened account");
|
if (!isAccountOpen()) throw new IllegalStateException("Cannot close unopened account");
|
||||||
keyRing.lockKeys(); // closed account means the keys are locked
|
keyRing.lockKeys(); // closed account means the keys are locked
|
||||||
for (AccountServiceListener listener : new ArrayList<AccountServiceListener>(listeners)) listener.onAccountClosed();
|
synchronized (listeners) {
|
||||||
|
for (AccountServiceListener listener : listeners) listener.onAccountClosed();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void backupAccount(int bufferSize, Consumer<InputStream> consume, Consumer<Exception> error) {
|
public void backupAccount(int bufferSize, Consumer<InputStream> consume, Consumer<Exception> error) {
|
||||||
|
@ -147,13 +157,17 @@ public class CoreAccountService {
|
||||||
if (accountExists()) throw new IllegalStateException("Cannot restore account if there is an existing account");
|
if (accountExists()) throw new IllegalStateException("Cannot restore account if there is an existing account");
|
||||||
File dataDir = new File(config.appDataDir.getPath());
|
File dataDir = new File(config.appDataDir.getPath());
|
||||||
ZipUtils.unzipToDir(dataDir, inputStream, bufferSize);
|
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) {
|
public void deleteAccount(Runnable onShutdown) {
|
||||||
try {
|
try {
|
||||||
keyRing.lockKeys();
|
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
|
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);
|
FileUtil.deleteDirectory(dataDir, null, false);
|
||||||
} catch (Exception err) {
|
} catch (Exception err) {
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package bisq.daemon.app;
|
package bisq.core.app;
|
||||||
|
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package bisq.daemon.app;
|
package bisq.core.app;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
|
@ -19,6 +19,7 @@ package bisq.core.app;
|
||||||
|
|
||||||
import bisq.core.api.AccountServiceListener;
|
import bisq.core.api.AccountServiceListener;
|
||||||
import bisq.core.api.CoreAccountService;
|
import bisq.core.api.CoreAccountService;
|
||||||
|
import bisq.core.api.CoreMoneroConnectionsService;
|
||||||
import bisq.core.btc.setup.WalletsSetup;
|
import bisq.core.btc.setup.WalletsSetup;
|
||||||
import bisq.core.btc.wallet.BtcWalletService;
|
import bisq.core.btc.wallet.BtcWalletService;
|
||||||
import bisq.core.btc.wallet.XmrWalletService;
|
import bisq.core.btc.wallet.XmrWalletService;
|
||||||
|
@ -48,7 +49,11 @@ import bisq.common.util.Utilities;
|
||||||
|
|
||||||
import com.google.inject.Guice;
|
import com.google.inject.Guice;
|
||||||
import com.google.inject.Injector;
|
import com.google.inject.Injector;
|
||||||
|
|
||||||
|
import java.io.Console;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
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_SUCCESS = 0;
|
||||||
public static final int EXIT_FAILURE = 1;
|
public static final int EXIT_FAILURE = 1;
|
||||||
|
public static final int EXIT_RESTART = 2;
|
||||||
|
|
||||||
private final String fullName;
|
private final String fullName;
|
||||||
private final String scriptName;
|
private final String scriptName;
|
||||||
|
@ -72,6 +78,9 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
|
||||||
protected Config config;
|
protected Config config;
|
||||||
private boolean isShutdownInProgress;
|
private boolean isShutdownInProgress;
|
||||||
private boolean isReadOnly;
|
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) {
|
public HavenoExecutable(String fullName, String scriptName, String appName, String version) {
|
||||||
this.fullName = fullName;
|
this.fullName = fullName;
|
||||||
|
@ -80,7 +89,7 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
|
||||||
this.version = version;
|
this.version = version;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void execute(String[] args) {
|
public int execute(String[] args) {
|
||||||
try {
|
try {
|
||||||
config = new Config(appName, Utilities.getUserDataDir(), args);
|
config = new Config(appName, Utilities.getUserDataDir(), args);
|
||||||
if (config.helpRequested) {
|
if (config.helpRequested) {
|
||||||
|
@ -98,14 +107,14 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
|
||||||
System.exit(EXIT_FAILURE);
|
System.exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
doExecute();
|
return doExecute();
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// First synchronous execution tasks
|
// First synchronous execution tasks
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
protected void doExecute() {
|
protected int doExecute() {
|
||||||
CommonSetup.setup(config, this);
|
CommonSetup.setup(config, this);
|
||||||
CoreSetup.setup(config);
|
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
|
// If application is JavaFX application we need to wait until it is initialized
|
||||||
launchApplication();
|
launchApplication();
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void configUserThread();
|
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.
|
// Application needs to restart on delete and restore of account.
|
||||||
accountService.addListener(new AccountServiceListener() {
|
accountService.addListener(new AccountServiceListener() {
|
||||||
@Override public void onAccountDeleted(Runnable onShutdown) { shutDownNoPersist(onShutdown); }
|
@Override public void onAccountDeleted(Runnable onShutdown) { shutDownNoPersist(onShutdown, true); }
|
||||||
@Override public void onAccountRestored(Runnable onShutdown) { shutDownNoPersist(onShutdown); }
|
@Override public void onAccountRestored(Runnable onShutdown) { shutDownNoPersist(onShutdown, true); }
|
||||||
});
|
});
|
||||||
|
|
||||||
// Attempt to login, subclasses should implement interactive login and or rpc login.
|
// 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
|
* Do not persist when shutting down after account restore and restarts since
|
||||||
* that causes the current persistables to overwrite the restored or deleted state.
|
* 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;
|
this.isReadOnly = true;
|
||||||
gracefulShutDown(() -> {
|
if (restart) {
|
||||||
log.info("Shutdown without persisting");
|
shutdownCompletedHandler = onShutdown;
|
||||||
if (onShutdown != null) onShutdown.run();
|
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
|
* Attempt to login. TODO: supply a password in config or args
|
||||||
*
|
*
|
||||||
* @return true if account is opened successfully.
|
* @return true if account is opened successfully.
|
||||||
*/
|
*/
|
||||||
protected boolean loginAccount() {
|
protected boolean loginAccount() {
|
||||||
|
@ -258,16 +278,32 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
|
||||||
// GracefulShutDownHandler implementation
|
// GracefulShutDownHandler implementation
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// This might need to be overwritten in case the application is not using all modules
|
|
||||||
@Override
|
@Override
|
||||||
public void gracefulShutDown(ResultHandler resultHandler) {
|
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");
|
log.info("Start graceful shutDown");
|
||||||
if (isShutdownInProgress) {
|
if (isShutdownInProgress) {
|
||||||
|
log.info("Ignoring call to gracefulShutDown, already in progress");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
isShutdownInProgress = true;
|
isShutdownInProgress = true;
|
||||||
|
|
||||||
|
ResultHandler resultHandler;
|
||||||
|
if (shutdownCompletedHandler != null) {
|
||||||
|
resultHandler = () -> {
|
||||||
|
shutdownCompletedHandler.run();
|
||||||
|
onShutdown.handleResult();
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
resultHandler = onShutdown;
|
||||||
|
}
|
||||||
|
|
||||||
if (injector == null) {
|
if (injector == null) {
|
||||||
log.info("Shut down called before injector was created");
|
log.info("Shut down called before injector was created");
|
||||||
resultHandler.handleResult();
|
resultHandler.handleResult();
|
||||||
|
@ -281,7 +317,7 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
|
||||||
injector.getInstance(XmrTxProofService.class).shutDown();
|
injector.getInstance(XmrTxProofService.class).shutDown();
|
||||||
injector.getInstance(AvoidStandbyModeService.class).shutDown();
|
injector.getInstance(AvoidStandbyModeService.class).shutDown();
|
||||||
injector.getInstance(TradeManager.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");
|
log.info("OpenOfferManager shutdown started");
|
||||||
injector.getInstance(OpenOfferManager.class).shutDown(() -> {
|
injector.getInstance(OpenOfferManager.class).shutDown(() -> {
|
||||||
log.info("OpenOfferManager shutdown completed");
|
log.info("OpenOfferManager shutdown completed");
|
||||||
|
@ -296,36 +332,31 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
|
||||||
injector.getInstance(P2PService.class).shutDown(() -> {
|
injector.getInstance(P2PService.class).shutDown(() -> {
|
||||||
log.info("P2PService shutdown completed");
|
log.info("P2PService shutdown completed");
|
||||||
module.close(injector);
|
module.close(injector);
|
||||||
completeShutdown(resultHandler, EXIT_SUCCESS);
|
completeShutdown(resultHandler, EXIT_SUCCESS, systemExit);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
walletsSetup.shutDown();
|
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) {
|
} catch (Throwable t) {
|
||||||
log.error("App shutdown failed with exception {}", t.toString());
|
log.error("App shutdown failed with exception {}", t.toString());
|
||||||
t.printStackTrace();
|
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 (!isReadOnly) {
|
||||||
// If user tried to downgrade we do not write the persistable data to avoid data corruption
|
// If user tried to downgrade we do not write the persistable data to avoid data corruption
|
||||||
PersistenceManager.flushAllDataToDiskAtShutdown(() -> {
|
PersistenceManager.flushAllDataToDiskAtShutdown(() -> {
|
||||||
log.info("Graceful shutdown flushed persistence. Exiting now.");
|
log.info("Graceful shutdown flushed persistence. Exiting now.");
|
||||||
resultHandler.handleResult();
|
resultHandler.handleResult();
|
||||||
UserThread.runAfter(() -> System.exit(exitCode), 1);
|
if (systemExit)
|
||||||
|
UserThread.runAfter(() -> System.exit(exitCode), 1);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
resultHandler.handleResult();
|
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)
|
if (doShutDown)
|
||||||
gracefulShutDown(() -> log.info("gracefulShutDown complete"));
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,10 +47,10 @@ public class HavenoHeadlessAppMain extends HavenoExecutable {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doExecute() {
|
protected int doExecute() {
|
||||||
super.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
|
// In headless mode we don't have an async behaviour so we trigger the setup by calling onApplicationStarted
|
||||||
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) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,7 +97,7 @@ public abstract class ExecutableForAppWithP2p extends HavenoExecutable {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
injector.getInstance(WalletsSetup.class).shutDown();
|
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();
|
injector.getInstance(BtcWalletService.class).shutDown();
|
||||||
}));
|
}));
|
||||||
// we wait max 5 sec.
|
// we wait max 5 sec.
|
||||||
|
@ -187,16 +187,6 @@ public abstract class ExecutableForAppWithP2p extends HavenoExecutable {
|
||||||
}, TimeUnit.HOURS.toSeconds(2));
|
}, 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) {
|
protected void checkMemory(Config config, GracefulShutDownHandler gracefulShutDownHandler) {
|
||||||
int maxMemory = config.maxMemory;
|
int maxMemory = config.maxMemory;
|
||||||
UserThread.runPeriodically(() -> {
|
UserThread.runPeriodically(() -> {
|
||||||
|
|
|
@ -113,13 +113,15 @@ public class MoneroWalletRpcManager {
|
||||||
public void stopInstance(MoneroWalletRpc walletRpc) {
|
public void stopInstance(MoneroWalletRpc walletRpc) {
|
||||||
|
|
||||||
// unregister port
|
// unregister port
|
||||||
|
int port = -1;
|
||||||
synchronized (registeredPorts) {
|
synchronized (registeredPorts) {
|
||||||
boolean found = false;
|
boolean found = false;
|
||||||
for (Map.Entry<Integer, MoneroWalletRpc> entry : registeredPorts.entrySet()) {
|
for (Map.Entry<Integer, MoneroWalletRpc> entry : registeredPorts.entrySet()) {
|
||||||
if (walletRpc == entry.getValue()) {
|
if (walletRpc == entry.getValue()) {
|
||||||
found = true;
|
found = true;
|
||||||
try {
|
try {
|
||||||
unregisterPort(entry.getKey());
|
port = entry.getKey();
|
||||||
|
unregisterPort(port);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new MoneroError(e);
|
throw new MoneroError(e);
|
||||||
}
|
}
|
||||||
|
@ -130,6 +132,8 @@ public class MoneroWalletRpcManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
// stop process
|
// stop process
|
||||||
|
String pid = walletRpc.getProcess() == null ? null : String.valueOf(walletRpc.getProcess().pid());
|
||||||
|
log.info("Stopping MoneroWalletRpc port: {} pid: {}", port, pid);
|
||||||
walletRpc.stopProcess();
|
walletRpc.stopProcess();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -252,7 +252,6 @@ public class WalletConfig extends AbstractIdleService {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void shutDown() throws Exception {
|
protected void shutDown() throws Exception {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public NetworkParameters params() {
|
public NetworkParameters params() {
|
||||||
|
|
|
@ -146,7 +146,7 @@ public class XmrWalletService {
|
||||||
@Override
|
@Override
|
||||||
public void onAccountClosed() {
|
public void onAccountClosed() {
|
||||||
log.info(getClass() + ".accountService.onAccountClosed()");
|
log.info(getClass() + ".accountService.onAccountClosed()");
|
||||||
closeAllWallets();
|
closeAllWallets(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -304,7 +304,7 @@ public class XmrWalletService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thaw the given outputs with a lock on the wallet.
|
* Thaw the given outputs with a lock on the wallet.
|
||||||
*
|
*
|
||||||
* @param keyImages the key images to thaw
|
* @param keyImages the key images to thaw
|
||||||
*/
|
*/
|
||||||
public void thawOutputs(Collection<String> keyImages) {
|
public void thawOutputs(Collection<String> keyImages) {
|
||||||
|
@ -527,9 +527,9 @@ public class XmrWalletService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void shutDown() {
|
public void shutDown(boolean save) {
|
||||||
this.isShutDown = true;
|
this.isShutDown = true;
|
||||||
closeAllWallets();
|
closeAllWallets(save);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------ PRIVATE HELPERS -------------------------
|
// ------------------------------ 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);
|
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
|
// collect wallets to shutdown
|
||||||
List<MoneroWallet> openWallets = new ArrayList<MoneroWallet>();
|
List<MoneroWallet> openWallets = new ArrayList<MoneroWallet>();
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
package bisq.daemon.app;
|
package bisq.daemon.app;
|
||||||
|
|
||||||
|
import bisq.core.app.ConsoleInput;
|
||||||
import bisq.core.app.HavenoHeadlessAppMain;
|
import bisq.core.app.HavenoHeadlessAppMain;
|
||||||
import bisq.core.app.HavenoSetup;
|
import bisq.core.app.HavenoSetup;
|
||||||
import bisq.core.api.AccountServiceListener;
|
import bisq.core.api.AccountServiceListener;
|
||||||
|
@ -26,6 +27,7 @@ import bisq.common.UserThread;
|
||||||
import bisq.common.app.AppModule;
|
import bisq.common.app.AppModule;
|
||||||
import bisq.common.crypto.IncorrectPasswordException;
|
import bisq.common.crypto.IncorrectPasswordException;
|
||||||
import bisq.common.handlers.ResultHandler;
|
import bisq.common.handlers.ResultHandler;
|
||||||
|
import bisq.common.persistence.PersistenceManager;
|
||||||
|
|
||||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
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.Executors;
|
||||||
import java.util.concurrent.ThreadFactory;
|
import java.util.concurrent.ThreadFactory;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@ -45,7 +48,28 @@ public class HavenoDaemonMain extends HavenoHeadlessAppMain implements HavenoSet
|
||||||
private GrpcServer grpcServer;
|
private GrpcServer grpcServer;
|
||||||
|
|
||||||
public static void main(String[] args) {
|
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
|
@Override
|
||||||
public void gracefulShutDown(ResultHandler resultHandler) {
|
public void gracefulShutDown(ResultHandler resultHandler, boolean exit) {
|
||||||
super.gracefulShutDown(resultHandler);
|
super.gracefulShutDown(resultHandler, exit);
|
||||||
if (grpcServer != null) grpcServer.shutdown(); // could be null if application attempted to shutdown early
|
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
|
// Start rpc server in case login is coming in from rpc
|
||||||
grpcServer = injector.getInstance(GrpcServer.class);
|
grpcServer = injector.getInstance(GrpcServer.class);
|
||||||
grpcServer.start();
|
|
||||||
|
|
||||||
if (!opened) {
|
if (!opened) {
|
||||||
// Nonblocking, we need to stop if the login occurred through rpc.
|
// 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(() -> {
|
Thread t = new Thread(() -> {
|
||||||
interactiveLogin(reader);
|
interactiveLogin(reader);
|
||||||
});
|
});
|
||||||
t.start();
|
|
||||||
|
|
||||||
// Handle asynchronous account opens.
|
// Handle asynchronous account opens.
|
||||||
// Will need to also close and reopen account.
|
// Will need to also close and reopen account.
|
||||||
|
@ -143,10 +165,14 @@ public class HavenoDaemonMain extends HavenoHeadlessAppMain implements HavenoSet
|
||||||
};
|
};
|
||||||
accountService.addListener(accountListener);
|
accountService.addListener(accountListener);
|
||||||
|
|
||||||
|
// start server after the listener is registered
|
||||||
|
grpcServer.start();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Wait until interactive login or rpc. Check one more time if account is open to close race condition.
|
// Wait until interactive login or rpc. Check one more time if account is open to close race condition.
|
||||||
if (!accountService.isAccountOpen()) {
|
if (!accountService.isAccountOpen()) {
|
||||||
log.info("Interactive login required");
|
log.info("Interactive login required");
|
||||||
|
t.start();
|
||||||
t.join();
|
t.join();
|
||||||
}
|
}
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
|
@ -155,6 +181,8 @@ public class HavenoDaemonMain extends HavenoHeadlessAppMain implements HavenoSet
|
||||||
|
|
||||||
accountService.removeListener(accountListener);
|
accountService.removeListener(accountListener);
|
||||||
opened = accountService.isAccountOpen();
|
opened = accountService.isAccountOpen();
|
||||||
|
} else {
|
||||||
|
grpcServer.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
return opened;
|
return opened;
|
||||||
|
|
|
@ -60,12 +60,12 @@ public class SeedNodeMain extends ExecutableForAppWithP2p {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doExecute() {
|
protected int doExecute() {
|
||||||
super.doExecute();
|
super.doExecute();
|
||||||
|
|
||||||
checkMemory(config, this);
|
checkMemory(config, this);
|
||||||
|
|
||||||
keepRunning();
|
return keepRunning();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -40,12 +40,12 @@ public class StatisticsMain extends ExecutableForAppWithP2p {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doExecute() {
|
protected int doExecute() {
|
||||||
super.doExecute();
|
super.doExecute();
|
||||||
|
|
||||||
checkMemory(config, this);
|
checkMemory(config, this);
|
||||||
|
|
||||||
keepRunning();
|
return keepRunning();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
Loading…
Reference in a new issue