diff --git a/core/src/main/java/bisq/core/provider/ProvidersRepository.java b/core/src/main/java/bisq/core/provider/ProvidersRepository.java index 71b4f392..3cb33954 100644 --- a/core/src/main/java/bisq/core/provider/ProvidersRepository.java +++ b/core/src/main/java/bisq/core/provider/ProvidersRepository.java @@ -80,10 +80,14 @@ public class ProvidersRepository { } } - public void selectNextProviderBaseUrl() { + // returns true if provider selection loops to beginning + public boolean selectNextProviderBaseUrl() { + boolean looped = false; if (!providerList.isEmpty()) { - if (index >= providerList.size()) + if (index >= providerList.size()) { index = 0; + looped = true; + } baseUrl = providerList.get(index); index++; @@ -95,6 +99,7 @@ public class ProvidersRepository { log.warn("We do not have any providers. That can be if all providers are filtered or providersFromProgramArgs is set but empty. " + "bannedNodes={}. providersFromProgramArgs={}", bannedNodes, providersFromProgramArgs); } + return looped; } private void fillProviderList() { diff --git a/core/src/main/java/bisq/core/provider/price/PriceFeedService.java b/core/src/main/java/bisq/core/provider/price/PriceFeedService.java index 88fb1674..319aa9da 100644 --- a/core/src/main/java/bisq/core/provider/price/PriceFeedService.java +++ b/core/src/main/java/bisq/core/provider/price/PriceFeedService.java @@ -22,16 +22,15 @@ import bisq.core.locale.TradeCurrency; import bisq.core.monetary.Price; import bisq.core.provider.PriceHttpClient; import bisq.core.provider.ProvidersRepository; +import bisq.core.trade.HavenoUtils; import bisq.core.trade.statistics.TradeStatistics3; import bisq.core.user.Preferences; import bisq.network.http.HttpClient; - import bisq.common.Timer; import bisq.common.UserThread; import bisq.common.handlers.FaultHandler; import bisq.common.util.MathUtils; -import bisq.common.util.Tuple2; import com.google.inject.Inject; @@ -45,6 +44,7 @@ import javafx.beans.property.ReadOnlyIntegerProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; +import javafx.beans.value.ChangeListener; import java.time.Instant; @@ -57,8 +57,8 @@ import java.util.Map; import java.util.Random; import java.util.Set; import java.util.concurrent.CancellationException; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.Consumer; @@ -88,14 +88,16 @@ public class PriceFeedService { private final StringProperty currencyCodeProperty = new SimpleStringProperty(); private final IntegerProperty updateCounter = new SimpleIntegerProperty(0); private long epochInMillisAtLastRequest; - private long retryDelay = 1; + private long retryDelay = 0; private long requestTs; + private long lastLoopTs = System.currentTimeMillis(); @Nullable private String baseUrlOfRespondingProvider; @Nullable private Timer requestTimer; @Nullable private PriceRequest priceRequest; + private String requestAllPricesError = null; /////////////////////////////////////////////////////////////////////////////////////////// @@ -236,25 +238,33 @@ public class PriceFeedService { } private void retryWithNewProvider() { - // We increase retry delay each time until we reach PERIOD_SEC to not exceed requests. + long thisRetryDelay = 0; + String oldBaseUrl = priceProvider.getBaseUrl(); + boolean looped = setNewPriceProvider(); + if (looped) { + if (System.currentTimeMillis() - lastLoopTs < PERIOD_SEC * 1000) { + retryDelay = Math.min(retryDelay + 5, PERIOD_SEC); + } else { + retryDelay = 0; + } + lastLoopTs = System.currentTimeMillis(); + thisRetryDelay = retryDelay; + } + log.warn("We received an error at the request from provider {}. " + + "We select the new provider {} and use that for a new request in {} sec.", oldBaseUrl, priceProvider.getBaseUrl(), thisRetryDelay); UserThread.runAfter(() -> { - retryDelay = Math.min(retryDelay + 5, PERIOD_SEC); - - String oldBaseUrl = priceProvider.getBaseUrl(); - setNewPriceProvider(); - log.warn("We received an error at the request from provider {}. " + - "We select the new provider {} and use that for a new request. retryDelay was {} sec.", oldBaseUrl, priceProvider.getBaseUrl(), retryDelay); - request(true); - }, retryDelay); + }, thisRetryDelay); } - private void setNewPriceProvider() { - providersRepository.selectNextProviderBaseUrl(); + // returns true if provider selection loops back to beginning + private boolean setNewPriceProvider() { + boolean looped = providersRepository.selectNextProviderBaseUrl(); if (!providersRepository.getBaseUrl().isEmpty()) priceProvider = new PriceProvider(httpClient, providersRepository.getBaseUrl()); else log.warn("We cannot create a new priceProvider because new base url is empty."); + return looped; } @Nullable @@ -333,12 +343,25 @@ public class PriceFeedService { /** * Returns prices for all available currencies. * For crypto currencies the value is XMR price for 1 unit of given crypto currency (e.g. 1 DOGE = X XMR). - * For fiat currencies the value is price in the given fiiat currency per 1 XMR (e.g. 1 XMR = X USD). - * Does not update PriceFeedService internal state (cache, epochInMillisAtLastRequest) + * For fiat currencies the value is price in the given fiat currency per 1 XMR (e.g. 1 XMR = X USD). + * + * TODO: instrument requestPrices() result and fault handlers instead of using CountDownLatch and timeout */ - public Map requestAllPrices() throws ExecutionException, InterruptedException, TimeoutException, CancellationException { - return new PriceRequest().requestAllPrices(priceProvider) - .get(20, TimeUnit.SECONDS); + public synchronized Map requestAllPrices() throws ExecutionException, InterruptedException, TimeoutException, CancellationException { + CountDownLatch latch = new CountDownLatch(1); + ChangeListener listener = (observable, oldValue, newValue) -> { latch.countDown(); }; + updateCounter.addListener(listener); + requestAllPricesError = null; + requestPrices(); + UserThread.runAfter(() -> { + if (latch.getCount() == 0) return; + requestAllPricesError = "Timeout fetching market prices within 20 seconds"; + latch.countDown(); + }, 20); + HavenoUtils.awaitLatch(latch); + updateCounter.removeListener(listener); + if (requestAllPricesError != null) throw new RuntimeException(requestAllPricesError); + return cache; } /////////////////////////////////////////////////////////////////////////////////////////// @@ -350,6 +373,7 @@ public class PriceFeedService { String errorMessage = null; if (currencyCode != null) { String baseUrl = priceProvider.getBaseUrl(); + httpClient.setBaseUrl(baseUrl); if (cache.containsKey(currencyCode)) { try { MarketPrice marketPrice = cache.get(currencyCode); diff --git a/core/src/main/java/bisq/core/provider/price/PriceProvider.java b/core/src/main/java/bisq/core/provider/price/PriceProvider.java index 5a057673..708022a4 100644 --- a/core/src/main/java/bisq/core/provider/price/PriceProvider.java +++ b/core/src/main/java/bisq/core/provider/price/PriceProvider.java @@ -17,7 +17,6 @@ package bisq.core.provider.price; -import bisq.core.locale.CurrencyUtil; import bisq.core.provider.HttpClientProvider; import bisq.network.http.HttpClient; @@ -25,7 +24,6 @@ import bisq.network.p2p.P2PService; import bisq.common.app.Version; import bisq.common.util.MathUtils; -import bisq.common.util.Tuple2; import com.google.gson.Gson; import com.google.gson.internal.LinkedTreeMap;