fix(market): back off failed market fetches instead of retrying every frame
The Market tab calls the exchange-list, price-history and candle fetches every frame, so a failed fetch (offline, rate-limited, a venue's API down) was retried on the very next frame, ~20 requests a second. - util::FetchBackoff: 30 s doubling to a 10 min cap, +/-20% jitter. The exchange list, the price history (a partial result counts as a failure) and the per-pair candles each back off on failure. - Candle backoff is per pair. Switching to another pair still loads at once, a failed refresh keeps the on-screen pair's last candles, and switching back to a pair that is backing off shows the aggregate chart rather than the previous pair's candles. - The Market tab's refresh button retries at once (at most every 5 s). A price fetch (60 s timer, no backoff) that succeeds after failing means the network is back, so it lifts all three backoffs. - A partial price history no longer marks the chart loaded: with no fetch timestamp, the 30-minute reload gate measured system uptime and blocked retries for the first 30 minutes after boot. - The exchange list goes through httpGetString (TLS verified, size capped) with short timeouts (5 s connect, 12 s total, as it runs on the shared RPC worker) instead of its own curl code. A failed fetch is retried; it used to be tried only once per session. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
#include "daemon/daemon_busy_detect.h"
|
||||
#include "daemon/embedded_daemon.h"
|
||||
#include "util/connect_stall.h"
|
||||
#include "util/fetch_backoff.h"
|
||||
#include "util/logger.h"
|
||||
#include "data/transaction_history_cache.h"
|
||||
#include "data/address_book.h"
|
||||
@@ -4374,6 +4375,41 @@ void testRpcKindClasses()
|
||||
}
|
||||
}
|
||||
|
||||
// Failed market fetches back off exponentially instead of retrying every frame.
|
||||
void testFetchBackoff()
|
||||
{
|
||||
using dragonx::util::FetchBackoff;
|
||||
using Clock = FetchBackoff::Clock;
|
||||
using std::chrono::seconds;
|
||||
const Clock::time_point t0 = Clock::time_point{} + std::chrono::hours(1);
|
||||
|
||||
FetchBackoff b;
|
||||
EXPECT_TRUE(b.due(t0)); // nothing failed yet
|
||||
b.fail(t0); // 1st failure: 30 s
|
||||
EXPECT_FALSE(b.due(t0 + seconds(29)));
|
||||
EXPECT_TRUE(b.due(t0 + seconds(30)));
|
||||
b.fail(t0); // 2nd: 60 s
|
||||
EXPECT_FALSE(b.due(t0 + seconds(59)));
|
||||
EXPECT_TRUE(b.due(t0 + seconds(60)));
|
||||
for (int i = 0; i < 20; ++i) b.fail(t0); // many failures: capped at 10 min
|
||||
EXPECT_FALSE(b.due(t0 + seconds(599)));
|
||||
EXPECT_TRUE(b.due(t0 + seconds(600)));
|
||||
b.forceDue(); // manual retry: allowed now...
|
||||
EXPECT_TRUE(b.due(t0));
|
||||
EXPECT_TRUE(b.failures() > 0); // ...but the next failure still waits long
|
||||
b.succeed();
|
||||
EXPECT_TRUE(b.due(t0));
|
||||
EXPECT_EQ(b.failures(), 0);
|
||||
|
||||
// Jitter scales the wait by at most +/-20%.
|
||||
EXPECT_TRUE(FetchBackoff::delayFor(1, 1.0) == std::chrono::duration_cast<Clock::duration>(seconds(36)));
|
||||
EXPECT_TRUE(FetchBackoff::delayFor(1, -1.0) == std::chrono::duration_cast<Clock::duration>(seconds(24)));
|
||||
for (int i = 0; i < 50; ++i) {
|
||||
const double j = FetchBackoff::randomJitter();
|
||||
EXPECT_TRUE(j >= -1.0 && j <= 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
void testConnectRetryBackoff()
|
||||
{
|
||||
using dragonx::rpc::ConnectRetryBackoff;
|
||||
@@ -9465,6 +9501,7 @@ int main()
|
||||
testRpcBusyGate();
|
||||
testRpcKindClasses();
|
||||
testConnectRetryBackoff();
|
||||
testFetchBackoff();
|
||||
testNetworkRefreshService();
|
||||
testNetworkRefreshSnapshotHelpers();
|
||||
testParseDaemonRescanOutput();
|
||||
|
||||
Reference in New Issue
Block a user