Files
ObsidianDragon/src/services/refresh_scheduler.h
T
DanSandClaude Opus 5.5 323143e111 perf(net): one scan budget for all wallet scans, gated on what changed
The balance, address scan, history and probe were throttled one by one at
10% each, which added up to 30-40% of dragonxd's cs_main with all of them
due. services::WalletScanGovernor runs one wallet scan at a time across
both RPC lanes and keeps one shared quiet clock at 15% of wall-clock (cut
when the node is behind). Waiters are served first in, first out, with a
head start for what the page shows and a starvation floor of max(30 s
visible / 900 s hidden, 15x the stream's cost). Explicit work (a dirty
set, the user's send, a first load) is never refused but is charged.

Routine scans run only when the tip, the mempool size (getmempoolinfo:
no cs_main) or a wallet-mutation epoch changed since their last good scan,
or a safety sweep is due. Pages that don't show the balance poll it at
most every max(10 min, 50x its cost) (2 min with client-side auto-shield);
coming back into view with an old value fetches it at once. The staleness
badge counts from the last poll that confirmed nothing changed, and hides
while the budget holds the balance within its guarantee.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 11:06:34 -05:00

104 lines
3.7 KiB
C++

#pragma once
#include "ui/sidebar.h"
#include <array>
namespace dragonx {
namespace services {
class RefreshScheduler {
public:
enum class Timer {
Core,
Transactions,
Addresses,
Peers,
Price,
Fast,
TxAge,
Opid
};
struct Intervals {
float core;
float transactions;
float addresses;
float peers;
};
static constexpr float kCoreDefault = 5.0f;
static constexpr float kAddressDefault = 15.0f;
static constexpr float kTransactionDefault = 10.0f;
static constexpr float kPeerDefault = 10.0f;
static constexpr float kPrice = 60.0f;
static constexpr float kFast = 1.0f;
static constexpr float kTxMaxAge = 15.0f;
static constexpr float kOpidPoll = 2.0f;
// Low-impact polling profile applied while the daemon is SYNCING, regardless of the active tab.
// Only a slow progress poll runs (core, 10s); transactions/addresses/peers are disabled (0).
// Frequent getpeerinfo, per-block transaction scans, and balance polls all contend for the
// daemon's cs_main lock and measurably slow block connection during sync — this is exactly why
// the lightweight Console tab syncs faster than the Peers tab. Reverts to the per-tab profile
// once sync completes. (Tx/address/balance data is incomplete mid-sync anyway.)
static constexpr Intervals kSyncProfile{10.0f, 0.0f, 0.0f, 0.0f};
static Intervals intervalsForPage(ui::NavPage page);
void applyPage(ui::NavPage page);
void setIntervals(Intervals intervals);
const Intervals& intervals() const { return intervals_; }
void tick(float deltaSeconds);
bool isDue(Timer timer) const;
bool consumeDue(Timer timer);
void reset(Timer timer);
void markDue(Timer timer);
void setTimer(Timer timer, float seconds);
// Hold a timer for exactly `seconds`, whatever the page's interval (which only measures elapsed
// time): not due until then, due as soon as it runs out. consumeDue/reset/markDue/setTimer end it.
// For retry backoff: the old "setTimer(interval - delay)" collapsed to an immediate retry wherever
// the page interval was shorter than the delay (every frame on Overview/Send).
void deferFor(Timer timer, float seconds);
// Due again `seconds` from now (a scan the governor held back asks again soon). Unlike deferFor, never
// due on a 0-interval timer (the sync profile's), so a held-back scan can't fire mid-sync.
void retrySoon(Timer timer, float seconds);
float timer(Timer timer) const;
float interval(Timer timer) const;
void markImmediateRefresh();
void markWalletMutationRefresh();
void resetTxAge();
bool shouldRefreshTransactions(int lastTxBlockHeight,
int currentBlockHeight,
bool transactionsDirty) const;
private:
struct Timers {
float core = 0.0f;
float transactions = 0.0f;
float addresses = 0.0f;
float peers = 0.0f;
float price = 0.0f;
float fast = 0.0f;
float txAge = 0.0f;
float opid = 0.0f;
};
float& timerRef(Timer timer);
const float& timerRef(Timer timer) const;
static constexpr int kTimerCount = 8;
float& holdRef(Timer timer) { return holds_[static_cast<int>(timer)]; }
float hold(Timer timer) const { return holds_[static_cast<int>(timer)]; }
Intervals intervals_{kCoreDefault, kTransactionDefault, kAddressDefault, kPeerDefault};
Timers timers_;
// deferFor() holds: > 0 counting down (not due), 0 ran out (due), < 0 none.
std::array<float, kTimerCount> holds_{{-1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f}};
};
} // namespace services
} // namespace dragonx