PriceFeedService.requestAllPrices() cycles providers and updates cache

This commit is contained in:
woodser 2022-12-23 08:52:33 +00:00
parent ad17228b38
commit fb1b8f8bd8
3 changed files with 51 additions and 24 deletions

View File

@ -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 (!providerList.isEmpty()) {
if (index >= providerList.size()) if (index >= providerList.size()) {
index = 0; index = 0;
looped = true;
}
baseUrl = providerList.get(index); baseUrl = providerList.get(index);
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. " + 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); "bannedNodes={}. providersFromProgramArgs={}", bannedNodes, providersFromProgramArgs);
} }
return looped;
} }
private void fillProviderList() { private void fillProviderList() {

View File

@ -22,16 +22,15 @@ import bisq.core.locale.TradeCurrency;
import bisq.core.monetary.Price; import bisq.core.monetary.Price;
import bisq.core.provider.PriceHttpClient; import bisq.core.provider.PriceHttpClient;
import bisq.core.provider.ProvidersRepository; import bisq.core.provider.ProvidersRepository;
import bisq.core.trade.HavenoUtils;
import bisq.core.trade.statistics.TradeStatistics3; import bisq.core.trade.statistics.TradeStatistics3;
import bisq.core.user.Preferences; import bisq.core.user.Preferences;
import bisq.network.http.HttpClient; import bisq.network.http.HttpClient;
import bisq.common.Timer; import bisq.common.Timer;
import bisq.common.UserThread; import bisq.common.UserThread;
import bisq.common.handlers.FaultHandler; import bisq.common.handlers.FaultHandler;
import bisq.common.util.MathUtils; import bisq.common.util.MathUtils;
import bisq.common.util.Tuple2;
import com.google.inject.Inject; import com.google.inject.Inject;
@ -45,6 +44,7 @@ import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty; import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import java.time.Instant; import java.time.Instant;
@ -57,8 +57,8 @@ import java.util.Map;
import java.util.Random; import java.util.Random;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CancellationException; import java.util.concurrent.CancellationException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -88,14 +88,16 @@ public class PriceFeedService {
private final StringProperty currencyCodeProperty = new SimpleStringProperty(); private final StringProperty currencyCodeProperty = new SimpleStringProperty();
private final IntegerProperty updateCounter = new SimpleIntegerProperty(0); private final IntegerProperty updateCounter = new SimpleIntegerProperty(0);
private long epochInMillisAtLastRequest; private long epochInMillisAtLastRequest;
private long retryDelay = 1; private long retryDelay = 0;
private long requestTs; private long requestTs;
private long lastLoopTs = System.currentTimeMillis();
@Nullable @Nullable
private String baseUrlOfRespondingProvider; private String baseUrlOfRespondingProvider;
@Nullable @Nullable
private Timer requestTimer; private Timer requestTimer;
@Nullable @Nullable
private PriceRequest priceRequest; private PriceRequest priceRequest;
private String requestAllPricesError = null;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -236,25 +238,33 @@ public class PriceFeedService {
} }
private void retryWithNewProvider() { private void retryWithNewProvider() {
// We increase retry delay each time until we reach PERIOD_SEC to not exceed requests. long thisRetryDelay = 0;
UserThread.runAfter(() -> {
retryDelay = Math.min(retryDelay + 5, PERIOD_SEC);
String oldBaseUrl = priceProvider.getBaseUrl(); String oldBaseUrl = priceProvider.getBaseUrl();
setNewPriceProvider(); 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 {}. " + 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); "We select the new provider {} and use that for a new request in {} sec.", oldBaseUrl, priceProvider.getBaseUrl(), thisRetryDelay);
UserThread.runAfter(() -> {
request(true); request(true);
}, retryDelay); }, thisRetryDelay);
} }
private void setNewPriceProvider() { // returns true if provider selection loops back to beginning
providersRepository.selectNextProviderBaseUrl(); private boolean setNewPriceProvider() {
boolean looped = providersRepository.selectNextProviderBaseUrl();
if (!providersRepository.getBaseUrl().isEmpty()) if (!providersRepository.getBaseUrl().isEmpty())
priceProvider = new PriceProvider(httpClient, providersRepository.getBaseUrl()); priceProvider = new PriceProvider(httpClient, providersRepository.getBaseUrl());
else else
log.warn("We cannot create a new priceProvider because new base url is empty."); log.warn("We cannot create a new priceProvider because new base url is empty.");
return looped;
} }
@Nullable @Nullable
@ -333,12 +343,25 @@ public class PriceFeedService {
/** /**
* Returns prices for all available currencies. * 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 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). * For fiat currencies the value is price in the given fiat currency per 1 XMR (e.g. 1 XMR = X USD).
* Does not update PriceFeedService internal state (cache, epochInMillisAtLastRequest) *
* TODO: instrument requestPrices() result and fault handlers instead of using CountDownLatch and timeout
*/ */
public Map<String, MarketPrice> requestAllPrices() throws ExecutionException, InterruptedException, TimeoutException, CancellationException { public synchronized Map<String, MarketPrice> requestAllPrices() throws ExecutionException, InterruptedException, TimeoutException, CancellationException {
return new PriceRequest().requestAllPrices(priceProvider) CountDownLatch latch = new CountDownLatch(1);
.get(20, TimeUnit.SECONDS); ChangeListener<? super Number> 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; String errorMessage = null;
if (currencyCode != null) { if (currencyCode != null) {
String baseUrl = priceProvider.getBaseUrl(); String baseUrl = priceProvider.getBaseUrl();
httpClient.setBaseUrl(baseUrl);
if (cache.containsKey(currencyCode)) { if (cache.containsKey(currencyCode)) {
try { try {
MarketPrice marketPrice = cache.get(currencyCode); MarketPrice marketPrice = cache.get(currencyCode);

View File

@ -17,7 +17,6 @@
package bisq.core.provider.price; package bisq.core.provider.price;
import bisq.core.locale.CurrencyUtil;
import bisq.core.provider.HttpClientProvider; import bisq.core.provider.HttpClientProvider;
import bisq.network.http.HttpClient; import bisq.network.http.HttpClient;
@ -25,7 +24,6 @@ import bisq.network.p2p.P2PService;
import bisq.common.app.Version; import bisq.common.app.Version;
import bisq.common.util.MathUtils; import bisq.common.util.MathUtils;
import bisq.common.util.Tuple2;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.internal.LinkedTreeMap; import com.google.gson.internal.LinkedTreeMap;