Optimize startup performance

This commit is contained in:
Manfred Karrer 2017-07-16 22:39:15 +02:00
parent 484b5411b9
commit 2fe77aadd4
3 changed files with 92 additions and 49 deletions

View file

@ -64,14 +64,12 @@ import io.bisq.core.trade.statistics.TradeStatisticsManager;
import io.bisq.core.user.DontShowAgainLookup; import io.bisq.core.user.DontShowAgainLookup;
import io.bisq.core.user.Preferences; import io.bisq.core.user.Preferences;
import io.bisq.core.user.User; import io.bisq.core.user.User;
import io.bisq.gui.app.BisqApp;
import io.bisq.gui.common.model.ViewModel; import io.bisq.gui.common.model.ViewModel;
import io.bisq.gui.components.BalanceWithConfirmationTextField; import io.bisq.gui.components.BalanceWithConfirmationTextField;
import io.bisq.gui.components.TxIdTextField; import io.bisq.gui.components.TxIdTextField;
import io.bisq.gui.main.overlays.notifications.NotificationCenter; import io.bisq.gui.main.overlays.notifications.NotificationCenter;
import io.bisq.gui.main.overlays.popups.Popup; import io.bisq.gui.main.overlays.popups.Popup;
import io.bisq.gui.main.overlays.windows.DisplayAlertMessageWindow; import io.bisq.gui.main.overlays.windows.DisplayAlertMessageWindow;
import io.bisq.gui.main.overlays.windows.SelectBaseCurrencyWindow;
import io.bisq.gui.main.overlays.windows.TacWindow; import io.bisq.gui.main.overlays.windows.TacWindow;
import io.bisq.gui.main.overlays.windows.WalletPasswordWindow; import io.bisq.gui.main.overlays.windows.WalletPasswordWindow;
import io.bisq.gui.main.overlays.windows.downloadupdate.DisplayUpdateDownloadWindow; import io.bisq.gui.main.overlays.windows.downloadupdate.DisplayUpdateDownloadWindow;
@ -192,6 +190,7 @@ public class MainViewModel implements ViewModel {
private BooleanProperty p2pNetWorkReady; private BooleanProperty p2pNetWorkReady;
private final BooleanProperty walletInitialized = new SimpleBooleanProperty(); private final BooleanProperty walletInitialized = new SimpleBooleanProperty();
private boolean allBasicServicesInitialized; private boolean allBasicServicesInitialized;
private BooleanProperty loadEntryMapResult, checkCryptoSetupResult;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -255,10 +254,6 @@ public class MainViewModel implements ViewModel {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public void start() { public void start() {
checkCryptoSetup();
}
private void showTacWindow() {
//noinspection ConstantConditions,ConstantConditions //noinspection ConstantConditions,ConstantConditions
if (!preferences.isTacAccepted() && !DevEnv.DEV_MODE) { if (!preferences.isTacAccepted() && !DevEnv.DEV_MODE) {
UserThread.runAfter(() -> { UserThread.runAfter(() -> {
@ -266,7 +261,7 @@ public class MainViewModel implements ViewModel {
preferences.setTacAccepted(true); preferences.setTacAccepted(true);
showSelectBaseCurrencyWindow(); showSelectBaseCurrencyWindow();
}).show(); }).show();
}, 2); }, 1);
} else { } else {
showSelectBaseCurrencyWindow(); showSelectBaseCurrencyWindow();
} }
@ -275,7 +270,11 @@ public class MainViewModel implements ViewModel {
private void showSelectBaseCurrencyWindow() { private void showSelectBaseCurrencyWindow() {
String key = "showSelectBaseCurrencyWindowAtFistStartup"; String key = "showSelectBaseCurrencyWindowAtFistStartup";
if (preferences.showAgain(key)) { if (preferences.showAgain(key)) {
new SelectBaseCurrencyWindow() bisqEnvironment.saveBaseCryptoNetwork(BisqEnvironment.getBaseCurrencyNetwork());
preferences.dontShowAgain(key, true);
showRevertIdCheckRequirement();
/* new SelectBaseCurrencyWindow()
.onSelect(baseCurrencyNetwork -> { .onSelect(baseCurrencyNetwork -> {
preferences.dontShowAgain(key, true); preferences.dontShowAgain(key, true);
final boolean hasChanged = !BisqEnvironment.getBaseCurrencyNetwork().equals(baseCurrencyNetwork); final boolean hasChanged = !BisqEnvironment.getBaseCurrencyNetwork().equals(baseCurrencyNetwork);
@ -294,49 +293,30 @@ public class MainViewModel implements ViewModel {
.onAction(() -> { .onAction(() -> {
bisqEnvironment.saveBaseCryptoNetwork(BisqEnvironment.getBaseCurrencyNetwork()); bisqEnvironment.saveBaseCryptoNetwork(BisqEnvironment.getBaseCurrencyNetwork());
preferences.dontShowAgain(key, true); preferences.dontShowAgain(key, true);
checkIfLocalHostNodeIsRunning(); showRevertIdCheckRequirement();
}) })
.hideCloseButton() .hideCloseButton()
.show(); .show();*/
} else { } else {
checkIfLocalHostNodeIsRunning(); showRevertIdCheckRequirement();
} }
} }
private void checkIfLocalHostNodeIsRunning() { private void showRevertIdCheckRequirement() {
Socket socket = null; //TODO remove after v0.5.2
try { String key = "revertIdCheckRequirement";
socket = new Socket(); if (preferences.showAgain(key) && Version.VERSION.equals("0.5.2") &&
socket.connect(new InetSocketAddress(InetAddresses.forString("127.0.0.1"), user.getPaymentAccounts() != null && !user.getPaymentAccounts().isEmpty())
BisqEnvironment.getBaseCurrencyNetwork().getParameters().getPort()), 5000); new Popup<>().information(Res.get("popup.info.revertIdCheckRequirement")).show();
log.info("Localhost peer detected."); preferences.dontShowAgain(key, true);
bisqEnvironment.setBitcoinLocalhostNodeRunning(true); checkIfLocalHostNodeIsRunning();
} catch (IOException e) {
log.info("Localhost peer not detected.");
} finally {
if (socket != null) {
try {
socket.close();
} catch (IOException ignore) {
}
}
startBasicServices();
}
} }
private void startBasicServices() { private void startBasicServices() {
log.info("startBasicServices"); log.info("startBasicServices");
//TODO remove after v0.5.2 loadEntryMapResult = loadEntryMap();
String key = "revertIdCheckRequirement"; checkCryptoSetupResult = checkCryptoSetup();
if (preferences.showAgain(key) && Version.VERSION.equals("0.5.2")) {
new Popup<>().information(Res.get("popup.info.revertIdCheckRequirement")).show();
preferences.dontShowAgain(key, true);
}
// Used to load different EntryMap files per base currency (EntryMap_BTC, EntryMap_LTC,...)
final String storageFileName = "EntryMap_" + BisqEnvironment.getBaseCurrencyNetwork().getCurrencyCode();
p2PService.readEntryMapFromResources(storageFileName);
ChangeListener<Boolean> walletInitializedListener = (observable, oldValue, newValue) -> { ChangeListener<Boolean> walletInitializedListener = (observable, oldValue, newValue) -> {
if (newValue && !p2pNetWorkReady.get()) if (newValue && !p2pNetWorkReady.get())
@ -359,7 +339,15 @@ public class MainViewModel implements ViewModel {
initWalletService(); initWalletService();
// need to store it to not get garbage collected // need to store it to not get garbage collected
allServicesDone = EasyBind.combine(walletInitialized, p2pNetWorkReady, (a, b) -> a && b); allServicesDone = EasyBind.combine(checkCryptoSetupResult, loadEntryMapResult, walletInitialized, p2pNetWorkReady,
(a, b, c, d) -> {
log.info("\ncheckCryptoSetupResult={}\n" +
"loadEntryMapResult={}\n" +
"walletInitialized={}\n" +
"p2pNetWorkReady={}",
a, b, c, d);
return a && b && c && d;
});
allServicesDone.subscribe((observable, oldValue, newValue) -> { allServicesDone.subscribe((observable, oldValue, newValue) -> {
if (newValue) { if (newValue) {
startupTimeout.stop(); startupTimeout.stop();
@ -393,6 +381,22 @@ public class MainViewModel implements ViewModel {
// Initialisation // Initialisation
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
private BooleanProperty loadEntryMap() {
BooleanProperty result = new SimpleBooleanProperty();
Thread loadEntryMapThread = new Thread() {
@Override
public void run() {
Thread.currentThread().setName("loadEntryMapThread");
// Used to load different EntryMap files per base currency (EntryMap_BTC, EntryMap_LTC,...)
final String storageFileName = "EntryMap_" + BisqEnvironment.getBaseCurrencyNetwork().getCurrencyCode();
p2PService.readEntryMapFromResources(storageFileName);
UserThread.execute(() -> result.set(true));
}
};
loadEntryMapThread.start();
return result;
}
private BooleanProperty initP2PNetwork() { private BooleanProperty initP2PNetwork() {
log.info("initP2PNetwork"); log.info("initP2PNetwork");
@ -727,7 +731,39 @@ public class MainViewModel implements ViewModel {
.show(); .show();
} }
private void checkCryptoSetup() { private void checkIfLocalHostNodeIsRunning() {
Thread checkIfLocalHostNodeIsRunningThread = new Thread() {
@Override
public void run() {
Thread.currentThread().setName("checkIfLocalHostNodeIsRunningThread");
Socket socket = null;
try {
socket = new Socket();
socket.connect(new InetSocketAddress(InetAddresses.forString("127.0.0.1"),
BisqEnvironment.getBaseCurrencyNetwork().getParameters().getPort()), 5000);
log.info("Localhost peer detected.");
UserThread.execute(() -> {
bisqEnvironment.setBitcoinLocalhostNodeRunning(true);
startBasicServices();
});
} catch (Throwable e) {
log.info("Localhost peer not detected.");
UserThread.execute(MainViewModel.this::startBasicServices);
} finally {
if (socket != null) {
try {
socket.close();
} catch (IOException ignore) {
}
}
}
}
};
checkIfLocalHostNodeIsRunningThread.start();
}
private BooleanProperty checkCryptoSetup() {
BooleanProperty result = new SimpleBooleanProperty();
// We want to test if the client is compiled with the correct crypto provider (BountyCastle) // We want to test if the client is compiled with the correct crypto provider (BountyCastle)
// and if the unlimited Strength for cryptographic keys is set. // and if the unlimited Strength for cryptographic keys is set.
// If users compile themselves they might miss that step and then would get an exception in the trade. // If users compile themselves they might miss that step and then would get an exception in the trade.
@ -750,7 +786,7 @@ public class MainViewModel implements ViewModel {
log.debug("Crypto test succeeded"); log.debug("Crypto test succeeded");
if (Security.getProvider("BC") != null) { if (Security.getProvider("BC") != null) {
UserThread.execute(MainViewModel.this::showTacWindow); UserThread.execute(() -> result.set(true));
} else { } else {
throw new CryptoException("Security provider BountyCastle is not available."); throw new CryptoException("Security provider BountyCastle is not available.");
} }
@ -769,6 +805,8 @@ public class MainViewModel implements ViewModel {
} }
}; };
checkCryptoThread.start(); checkCryptoThread.start();
return result;
} }
private void checkIfOpenOffersMatchTradeProtocolVersion() { private void checkIfOpenOffersMatchTradeProtocolVersion() {

View file

@ -97,11 +97,9 @@ public class P2PDataStorage implements MessageListener, ConnectionListener, Pers
// We get it called in readPersistedEntryMap once ready // We get it called in readPersistedEntryMap once ready
} }
public void readEntryMapFromResources(String resourceFileName) { // This method is called at startup in a non-user thread.
SequenceNumberMap persistedSequenceNumberMap = sequenceNumberMapStorage.initAndGetPersisted(sequenceNumberMap); // We should not have any threading issues here as the p2p network is just initializing
if (persistedSequenceNumberMap != null) public synchronized void readEntryMapFromResources(String resourceFileName) {
sequenceNumberMap.setMap(getPurgedSequenceNumberMap(persistedSequenceNumberMap.getMap()));
final String storageFileName = "EntryMap"; final String storageFileName = "EntryMap";
File dbDir = new File(storageDir.getAbsolutePath()); File dbDir = new File(storageDir.getAbsolutePath());
if (!dbDir.exists() && !dbDir.mkdir()) if (!dbDir.exists() && !dbDir.mkdir())
@ -110,6 +108,7 @@ public class P2PDataStorage implements MessageListener, ConnectionListener, Pers
final File destinationFile = new File(Paths.get(storageDir.getAbsolutePath(), storageFileName).toString()); final File destinationFile = new File(Paths.get(storageDir.getAbsolutePath(), storageFileName).toString());
if (!destinationFile.exists()) { if (!destinationFile.exists()) {
try { try {
log.info("We copy resource to file: resourceFileName={}, destinationFile={}", resourceFileName, destinationFile);
FileUtil.resourceToFile(resourceFileName, destinationFile); FileUtil.resourceToFile(resourceFileName, destinationFile);
} catch (ResourceNotFoundException e) { } catch (ResourceNotFoundException e) {
log.info("Could not find resourceFile " + resourceFileName + ". That is expected if none is provided yet."); log.info("Could not find resourceFile " + resourceFileName + ". That is expected if none is provided yet.");
@ -121,8 +120,9 @@ public class P2PDataStorage implements MessageListener, ConnectionListener, Pers
} else { } else {
log.debug(storageFileName + " file exists already."); log.debug(storageFileName + " file exists already.");
} }
// takes about 4 seconds with PB! :-(
persistedEntryMap = persistedEntryMapStorage.<HashMap<ByteArray, MapValue>>initAndGetPersistedWithFileName(storageFileName); persistedEntryMap = persistedEntryMapStorage.<HashMap<ByteArray, MapValue>>initAndGetPersistedWithFileName(storageFileName);
if (persistedEntryMap != null) { if (persistedEntryMap != null) {
map.putAll(persistedEntryMap.getMap()); map.putAll(persistedEntryMap.getMap());
log.info("persistedEntryMap size=" + map.size()); log.info("persistedEntryMap size=" + map.size());

View file

@ -23,12 +23,14 @@ import io.bisq.common.proto.persistable.PersistableEnvelope;
import io.bisq.generated.protobuffer.PB; import io.bisq.generated.protobuffer.PB;
import io.bisq.network.p2p.storage.payload.ProtectedStorageEntry; import io.bisq.network.p2p.storage.payload.ProtectedStorageEntry;
import lombok.Getter; import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Slf4j
public class PersistedEntryMap implements PersistableEnvelope { public class PersistedEntryMap implements PersistableEnvelope {
@Getter @Getter
private Map<P2PDataStorage.ByteArray, ProtectedStorageEntry> map = new ConcurrentHashMap<>(); private Map<P2PDataStorage.ByteArray, ProtectedStorageEntry> map = new ConcurrentHashMap<>();
@ -54,6 +56,9 @@ public class PersistedEntryMap implements PersistableEnvelope {
public static PersistableEnvelope fromProto(Map<String, PB.ProtectedStorageEntry> proto, public static PersistableEnvelope fromProto(Map<String, PB.ProtectedStorageEntry> proto,
NetworkProtoResolver networkProtoResolver) { NetworkProtoResolver networkProtoResolver) {
// Protobuffer maps don't support bytes as key so we use a hex string // Protobuffer maps don't support bytes as key so we use a hex string
// Takes about 4 sec for 4000 items ;-( Java serialisation was 500 ms
log.info("PersistedEntryMap.fromProto size: " + proto.entrySet().size());
Map<P2PDataStorage.ByteArray, ProtectedStorageEntry> map = proto.entrySet().stream() Map<P2PDataStorage.ByteArray, ProtectedStorageEntry> map = proto.entrySet().stream()
.collect(Collectors.<Map.Entry<String, PB.ProtectedStorageEntry>, P2PDataStorage.ByteArray, ProtectedStorageEntry>toMap( .collect(Collectors.<Map.Entry<String, PB.ProtectedStorageEntry>, P2PDataStorage.ByteArray, ProtectedStorageEntry>toMap(
e -> new P2PDataStorage.ByteArray(e.getKey()), e -> new P2PDataStorage.ByteArray(e.getKey()),