Skip to content

Commit

Permalink
Merge pull request bisq-network#1801 from HenrikJannsen/improve-shutd…
Browse files Browse the repository at this point in the history
…own-process

Improve shutdown process
  • Loading branch information
alvasw authored Mar 15, 2024
2 parents 86a73bb + db7eb96 commit 18de084
Show file tree
Hide file tree
Showing 16 changed files with 162 additions and 59 deletions.
7 changes: 7 additions & 0 deletions application/src/main/java/bisq/application/Executable.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,22 @@ public void shutdown() {
return;
}
shutDownStarted = true;
notifyAboutShutdown();
if (applicationService != null) {
applicationService.shutdown()
.thenRun(() -> {
shutDownHandlers.forEach(Runnable::run);
exitJvm();
});
} else {
shutDownHandlers.forEach(Runnable::run);
exitJvm();
}
}

protected void notifyAboutShutdown() {
}

protected void exitJvm() {
log.info("Exiting JVM");
System.exit(OsUtils.EXIT_SUCCESS);
Expand Down
10 changes: 6 additions & 4 deletions application/src/main/java/bisq/updater/UpdaterService.java
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,12 @@ public void clear() {

@Override
public CompletableFuture<Boolean> shutdown() {
if (executorService != null) {
ExecutorFactory.shutdownAndAwaitTermination(executorService, 100);
}
return CompletableFuture.completedFuture(true);
return CompletableFuture.supplyAsync(() -> {
if (executorService != null) {
ExecutorFactory.shutdownAndAwaitTermination(executorService, 100);
}
return true;
});
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import bisq.common.application.Service;
import bisq.common.observable.Observable;
import bisq.common.util.CompletableFutureUtils;
import bisq.common.util.ExceptionUtil;
import bisq.contract.ContractService;
import bisq.desktop.ServiceProvider;
import bisq.desktop.State;
Expand Down Expand Up @@ -63,10 +64,17 @@

@Slf4j
public class DesktopApplicationService extends ApplicationService {
public static final long STARTUP_TIMEOUT_SEC = 300;
public static final long SHUTDOWN_TIMEOUT_SEC = 10;

@Getter
private final ServiceProvider serviceProvider;
@Getter
private final Observable<State> state = new Observable<>(State.INITIALIZE_APP);
@Getter
private final Observable<String> shutDownErrorMessage = new Observable<>();
@Getter
private final Observable<String> startupErrorMessage = new Observable<>();

private final SecurityService securityService;
private final Optional<WalletService> walletService;
Expand Down Expand Up @@ -235,18 +243,20 @@ public CompletableFuture<Boolean> initialize() {
.thenCompose(result -> tradeService.initialize())
.thenCompose(result -> updaterService.initialize())
.thenCompose(result -> bisqEasyService.initialize())
.orTimeout(5, TimeUnit.MINUTES)
.orTimeout(STARTUP_TIMEOUT_SEC, TimeUnit.SECONDS)
.handle((result, throwable) -> {
if (throwable == null) {
if (result != null && result) {
setState(State.APP_INITIALIZED);
log.info("ApplicationService initialized");
return true;
} else {
log.error("Initializing applicationService failed");
startupErrorMessage.set("Initializing applicationService failed with result=false");
log.error(startupErrorMessage.get());
}
} else {
log.error("Initializing applicationService failed", throwable);
startupErrorMessage.set(ExceptionUtil.getMessageOrToString(throwable));
}
setState(State.FAILED);
return false;
Expand Down Expand Up @@ -274,13 +284,15 @@ public CompletableFuture<Boolean> shutdown() {
.thenCompose(result -> walletService.map(Service::shutdown)
.orElse(CompletableFuture.completedFuture(true)))
.thenCompose(result -> securityService.shutdown())
.orTimeout(10, TimeUnit.SECONDS)
.orTimeout(SHUTDOWN_TIMEOUT_SEC, TimeUnit.SECONDS)
.handle((result, throwable) -> {
if (throwable != null) {
log.error("Error at shutdown", throwable);
shutDownErrorMessage.set(ExceptionUtil.getMessageOrToString(throwable));
return false;
} else if (!result) {
log.error("Shutdown resulted with false");
shutDownErrorMessage.set("Shutdown failed with result=false");
log.error(startupErrorMessage.get());
return false;
}
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@

import bisq.application.Executable;
import bisq.desktop.DesktopController;
import bisq.desktop.common.threading.UIScheduler;
import bisq.desktop.common.threading.UIThread;
import bisq.desktop.components.overlay.Popup;
import bisq.i18n.Res;
import javafx.application.Application;
import javafx.application.Platform;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -33,6 +35,8 @@
public class DesktopExecutable extends Executable<DesktopApplicationService> {
@Nullable
private DesktopController desktopController;
@Nullable
private Popup shutdownInProcessPopup;

public DesktopExecutable(String[] args) {
super(args);
Expand All @@ -55,7 +59,9 @@ protected void launchApplication(String[] args) {
if (throwable == null) {
try {
log.info("Java FX Application launched");
desktopController = new DesktopController(applicationService.getState(), applicationService.getServiceProvider(),
setupStartupAndShutdownErrorHandlers();
desktopController = new DesktopController(applicationService.getState(),
applicationService.getServiceProvider(),
applicationData,
this::onApplicationLaunched);
desktopController.init();
Expand Down Expand Up @@ -95,11 +101,72 @@ protected void setDefaultUncaughtExceptionHandler() {
});
}

@Override
protected void notifyAboutShutdown() {
if (shutdownInProcessPopup != null) {
return;
}
try {
UIThread.run(() -> {
shutdownInProcessPopup = new Popup()
.headline(Res.get("action.shutDown"))
.feedback(Res.get("popup.shutdown", DesktopApplicationService.SHUTDOWN_TIMEOUT_SEC));
shutdownInProcessPopup.hideCloseButton().show();
});
} catch (Exception ignore) {
}
}

@Override
protected void exitJvm() {
if (applicationService.getShutDownErrorMessage().get() == null) {
doExit();
}
// If we have an error popup we leave it to the user to close it and shutdown the app by clicking the shutdown button
}

private void doExit() {
log.info("Exiting JavaFX Platform");
Platform.exit();

super.exitJvm();
}

private void setupStartupAndShutdownErrorHandlers() {
applicationService.getStartupErrorMessage().addObserver(errorMessage -> {
if (errorMessage != null) {
UIThread.run(() ->
new Popup().error(Res.get("popup.startup.error", errorMessage))
.closeButtonText(Res.get("action.shutDown"))
.onClose(this::doExit)
.show()
);
}
});
applicationService.getShutDownErrorMessage().addObserver(errorMessage -> {
if (errorMessage != null) {
UIThread.run(() -> {
if (shutdownInProcessPopup != null) {
shutdownInProcessPopup.hide();
shutdownInProcessPopup = null;
}
Popup popup = new Popup();
popup.error(Res.get("popup.shutdown.error", errorMessage))
.closeButtonText(Res.get("action.shutDown"))
.onClose(this::doExit)
.show();
// The error popup allow to report to GH, in that case we get closed the error popup.
// We leave the app open and reset the shutDownStarted flag, so that at another
// shutdown action shut down can happen. Only when the user clicks the close
// button we actually shut down.
popup.getIsHiddenProperty().addListener((observableValue, oldValue, newValue) -> {
if (newValue) {
shutDownStarted = false;
}
});
// We reset the error so that it can get triggered again our error popup in case the user shutdown again.
UIScheduler.run(() -> applicationService.getShutDownErrorMessage().set(null)).after(1000);
});
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,7 @@ public T error(Throwable throwable) {
public T error(String message) {
type = Type.ERROR;
showReportErrorButtons();
width = 1100;
width = 800;
if (headline == null)
this.headline = Res.get("popup.headline.error");
processMessage(message);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,24 +85,24 @@ public void onActivate() {

model.getSortedAccounts().setComparator(Comparator.comparing(Account::getAccountName));

accountsPin = accountService.getAccounts().addObserver(() -> {
List<UserDefinedFiatAccount> accounts = accountService.getAccounts().stream()
.filter(account -> account instanceof UserDefinedFiatAccount)
.map(account -> (UserDefinedFiatAccount) account)
.collect(Collectors.toList());
model.setAllAccounts(accounts);
model.getAccountSelectionVisible().set(accounts.size() > 1);
maybeSelectFirstAccount();
});
selectedAccountPin = accountService.selectedAccountAsObservable().addObserver(account -> {
UIThread.run(() -> {
if (account instanceof UserDefinedFiatAccount) {
UserDefinedFiatAccount userDefinedFiatAccount = (UserDefinedFiatAccount) account;
model.selectedAccountProperty().set(userDefinedFiatAccount);
model.getPaymentAccountData().set(userDefinedFiatAccount.getAccountPayload().getAccountData());
}
});
});
accountsPin = accountService.getAccounts().addObserver(() ->
UIThread.run(() -> {
List<UserDefinedFiatAccount> accounts = accountService.getAccounts().stream()
.filter(account -> account instanceof UserDefinedFiatAccount)
.map(account -> (UserDefinedFiatAccount) account)
.collect(Collectors.toList());
model.setAllAccounts(accounts);
model.getAccountSelectionVisible().set(accounts.size() > 1);
maybeSelectFirstAccount();
}));
selectedAccountPin = accountService.selectedAccountAsObservable().addObserver(account ->
UIThread.run(() -> {
if (account instanceof UserDefinedFiatAccount) {
UserDefinedFiatAccount userDefinedFiatAccount = (UserDefinedFiatAccount) account;
model.selectedAccountProperty().set(userDefinedFiatAccount);
model.getPaymentAccountData().set(userDefinedFiatAccount.getAccountPayload().getAccountData());
}
}));

model.getButtonDisabled().bind(model.getPaymentAccountData().isEmpty());
findUsersAccountData().ifPresent(accountData -> model.getPaymentAccountData().set(accountData));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import bisq.common.observable.Pin;
import bisq.desktop.ServiceProvider;
import bisq.desktop.common.observable.FxBindings;
import bisq.desktop.common.threading.UIThread;
import bisq.desktop.common.view.Controller;
import bisq.desktop.common.view.Navigation;
import bisq.i18n.Res;
Expand Down Expand Up @@ -61,13 +62,15 @@ public void onActivate() {
model.getSortedAccounts().setComparator(Comparator.comparing(Account::getAccountName));

accountsPin = accountService.getAccounts().addObserver(() -> {
model.setAllAccounts(accountService.getAccounts());
maybeSelectFirstAccount();
model.getNoAccountsSetup().set(!accountService.hasAccounts());
model.getHeadline().set(accountService.hasAccounts() ?
Res.get("user.paymentAccounts.headline") :
Res.get("user.paymentAccounts.noAccounts.headline")
);
UIThread.run(() -> {
model.setAllAccounts(accountService.getAccounts());
maybeSelectFirstAccount();
model.getNoAccountsSetup().set(!accountService.hasAccounts());
model.getHeadline().set(accountService.hasAccounts() ?
Res.get("user.paymentAccounts.headline") :
Res.get("user.paymentAccounts.noAccounts.headline")
);
});
});

selectedAccountPin = FxBindings.bind(model.selectedAccountProperty())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ public SplashController(Observable<State> applicationServiceState, ServiceProvid

@Override
public void onActivate() {
applicationServiceStatePin = applicationServiceState.addObserver(state -> {
UIThread.run(() -> model.getApplicationServiceState().set(Res.get("splash.applicationServiceState." + state.name())));
});
applicationServiceStatePin = applicationServiceState.addObserver(state ->
UIThread.run(() -> model.getApplicationServiceState().set(Res.get("splash.applicationServiceState." + state.name())))
);

if (networkService.getSupportedTransportTypes().contains(TransportType.CLEAR)) {
model.getBootstrapStateDisplays().add(new BootstrapStateDisplay(TransportType.CLEAR, serviceProvider));
Expand Down
4 changes: 4 additions & 0 deletions i18n/src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,10 @@ popup.reportError={0}\n\nTo help us to improve the software please report this b
The above error message will be copied to the clipboard when you click any of the buttons below.\n\
It will make debugging easier if you include the bisq.log file by pressing 'Open log file', saving a copy, and attaching it to your bug report.
popup.reportBug=Report bug to Bisq developers
popup.startup.error=An error occurred at initializing Bisq: {0}.
popup.shutdown=Shut down is in process.\n\n\
It might take up to {0} seconds until shut down is completed.
popup.shutdown.error=An error occurred at shut down: {0}.


####################################################################
Expand Down
15 changes: 9 additions & 6 deletions network/network/src/main/java/bisq/network/NetworkService.java
Original file line number Diff line number Diff line change
Expand Up @@ -205,12 +205,15 @@ public CompletableFuture<Boolean> initialize() {

public CompletableFuture<Boolean> shutdown() {
log.info("shutdown");
messageDeliveryStatusService.ifPresent(MessageDeliveryStatusService::shutdown);
resendMessageService.ifPresent(ResendMessageService::shutdown);
networkLoadService.ifPresent(NetworkLoadService::shutdown);
dataService.ifPresent(DataService::shutdown);
return serviceNodesByTransport.shutdown()
.thenApply(list -> list.stream().filter(e -> e).count() == supportedTransportTypes.size());
return CompletableFuture.supplyAsync(() -> {
messageDeliveryStatusService.ifPresent(MessageDeliveryStatusService::shutdown);
resendMessageService.ifPresent(ResendMessageService::shutdown);
networkLoadService.ifPresent(NetworkLoadService::shutdown);
dataService.ifPresent(DataService::shutdown);
return true;
})
.thenCompose(result -> serviceNodesByTransport.shutdown()
.thenApply(list -> list.stream().filter(e -> e).count() == supportedTransportTypes.size()));
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.TimeUnit;

import static com.google.common.base.Preconditions.checkArgument;
import static java.util.concurrent.CompletableFuture.runAsync;
Expand Down Expand Up @@ -245,8 +244,6 @@ CompletableFuture<Boolean> shutdown() {
inventoryService.ifPresent(InventoryService::shutdown);
confidentialMessageService.ifPresent(ConfidentialMessageService::shutdown);
return nodesById.shutdown()
.orTimeout(10, TimeUnit.SECONDS)
.handle((result, throwable) -> throwable == null && result)
.thenCompose(result -> transportService.shutdown())
.whenComplete((result, throwable) -> setState(State.TERMINATED));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,7 @@ public CompletableFuture<Boolean> shutdown() {
startBootstrapProgressUpdater.stop();
startBootstrapProgressUpdater = null;
}
torService.shutdown().join();
return CompletableFuture.completedFuture(true);
return torService.shutdown();
}

@Override
Expand Down
9 changes: 6 additions & 3 deletions network/tor/tor/src/main/java/bisq/tor/TorService.java
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,12 @@ public CompletableFuture<Boolean> initialize() {

@Override
public CompletableFuture<Boolean> shutdown() {
nativeTorController.shutdown();
torProcess.ifPresent(NativeTorProcess::waitUntilExited);
return CompletableFuture.completedFuture(true);
log.info("shutdown");
return CompletableFuture.supplyAsync(() -> {
nativeTorController.shutdown();
torProcess.ifPresent(NativeTorProcess::waitUntilExited);
return true;
});
}

public Observable<BootstrapEvent> getBootstrapEvent() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ public void waitUntilBootstrapped() {
}

public void shutdown() {
log.info("Send SHUTDOWN signal to tor control");
boolean canShutdown = isRunning.compareAndSet(true, false);
if (!canShutdown) {
return;
Expand Down
Loading

0 comments on commit 18de084

Please sign in to comment.